diff --git a/.changeset/brown-dingos-mix.md b/.changeset/brown-dingos-mix.md new file mode 100644 index 00000000..14101684 --- /dev/null +++ b/.changeset/brown-dingos-mix.md @@ -0,0 +1,7 @@ +--- +'squareone': minor +--- + +Implement background recomputation for cached Times Square pages. The "Recompute" button submits a request to Times Square's `DELETE /v1/pages/:page/html?{params}` endpoint, which causes a background recomputation of the notebook and re-rendering of the cached HTML. + +The new `TimesSquareHtmlEventsProvider` is a React context provider that provides real-time updates from Times Square about the status of an HTML rendering for a given set of parameters using Times Square's `/v1/pages/:page/html/events/{params}` endpoint. Squareone uses `@microsoft/fetch-event-source` to subscribe to this server-sent events (SSE) endpoint. Using this provider, the UI is able to show new data to the user, including the status of the computation, and once the computation is complete, the date/age of computation and the execution time. diff --git a/.changeset/chatty-bikes-unite.md b/.changeset/chatty-bikes-unite.md new file mode 100644 index 00000000..070246d0 --- /dev/null +++ b/.changeset/chatty-bikes-unite.md @@ -0,0 +1,5 @@ +--- +'squareone': minor +--- + +The Times Square "Update" and "Reset" buttons are now disabled when appropriate. The Update button is disabled when the parameter inputs have not been changed relative to their current state. Likewise, the Reset button is disabled when the parameters are unchanged from the current state. diff --git a/.changeset/seven-maps-drum.md b/.changeset/seven-maps-drum.md new file mode 100644 index 00000000..3b0e3cbb --- /dev/null +++ b/.changeset/seven-maps-drum.md @@ -0,0 +1,5 @@ +--- +'squareone': minor +--- + +New `TimesSquareUrlParametersProvider` component. This React context provides the URL-based state to Times Square components, such as the page being viewed, its notebook parameters values, and the display settings. This change simplifies the structure of the React pages by refactoring all of the URL parsing into a common component. As well, this context eliminates "prop drilling" to provide this URL-based state to all components in the Times Square application. diff --git a/apps/squareone/package.json b/apps/squareone/package.json index bef3bffd..822d86d5 100644 --- a/apps/squareone/package.json +++ b/apps/squareone/package.json @@ -42,9 +42,11 @@ "@lsst-sqre/global-css": "workspace:*", "@lsst-sqre/rubin-style-dictionary": "workspace:*", "@lsst-sqre/squared": "workspace:*", + "@microsoft/fetch-event-source": "^2.0.1", "@reach/alert": "^0.17.0", "@reach/menu-button": "^0.17.0", "ajv": "^8.11.0", + "date-fns": "^3.6.0", "formik": "^2.2.9", "js-yaml": "^4.1.0", "next": "^12.2.4", diff --git a/apps/squareone/src/components/Button/Button.js b/apps/squareone/src/components/Button/Button.js index c780bc79..5f079fd8 100644 --- a/apps/squareone/src/components/Button/Button.js +++ b/apps/squareone/src/components/Button/Button.js @@ -16,6 +16,9 @@ const Button = styled.button` &:active { background-color: var(--sqo-primary-button-background-color-active); } + &:disabled { + cursor: not-allowed; + } `; export default Button; @@ -36,4 +39,12 @@ export const GhostButton = styled.button` export const RedGhostButton = styled(GhostButton)` --ghost-button-color: var(--rsd-color-red-500); + + &:disabled { + // instead of setting opacity it'd be better to have a lighter red color + opacity: 0.5; + cursor: not-allowed; + background-color: transparent; + color: #000; + } `; diff --git a/apps/squareone/src/components/TimesSquareApp/TimesSquareApp.js b/apps/squareone/src/components/TimesSquareApp/TimesSquareApp.js index 983d195c..24cbdf2e 100644 --- a/apps/squareone/src/components/TimesSquareApp/TimesSquareApp.js +++ b/apps/squareone/src/components/TimesSquareApp/TimesSquareApp.js @@ -4,9 +4,14 @@ * page, usually the page viewer, or a markdown view of the GitHub repository. */ +import React from 'react'; import styled from 'styled-components'; import Sidebar from './Sidebar'; +import { TimesSquareUrlParametersContext } from '../TimesSquareUrlParametersProvider'; +import TimesSquareMainGitHubNav from '../TimesSquareMainGitHubNav'; +import TimesSquarePrGitHubNav from '../TimesSquarePrGitHubNav'; +import TimesSquareGitHubPagePanel from '../TimesSquareGitHubPagePanel/TimesSquareGitHubPagePanel'; const StyledLayout = styled.div` display: flex; @@ -20,10 +25,22 @@ const StyledLayout = styled.div` } `; -export default function TimesSquareApp({ children, pageNav, pagePanel }) { +export default function TimesSquareApp({ children }) { + const { tsSlug, owner, repo, commit, githubSlug, urlQueryString } = + React.useContext(TimesSquareUrlParametersContext); + + const pageNav = commit ? ( + + ) : ( + + ); + return ( - + : null} + />
{children}
); diff --git a/apps/squareone/src/components/TimesSquareGitHubPagePanel/ExecStats.js b/apps/squareone/src/components/TimesSquareGitHubPagePanel/ExecStats.js new file mode 100644 index 00000000..42d570bd --- /dev/null +++ b/apps/squareone/src/components/TimesSquareGitHubPagePanel/ExecStats.js @@ -0,0 +1,69 @@ +/* + * ExecStats provides a summary of the execution status and timing of the + * notebook execution. It also provides a button to request the recomputation + * of the already-executed notebook. + */ + +import React from 'react'; +import styled from 'styled-components'; +import { parseISO, formatDistanceToNow } from 'date-fns'; + +import { TimesSquareHtmlEventsContext } from '../TimesSquareHtmlEventsProvider'; +import { GhostButton } from '../Button'; + +export default function ExecStats({}) { + const htmlEvent = React.useContext(TimesSquareHtmlEventsContext); + + const handleRecompute = async (event) => { + event.preventDefault(); + + await fetch(htmlEvent.htmlUrl, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + }; + + if (htmlEvent.executionStatus == 'complete') { + const dateFinished = parseISO(htmlEvent.dateFinished); + const formattedDuration = Number(htmlEvent.executionDuration).toFixed(1); + return ( + + + Computed{' '} + {' '} + in {formattedDuration} seconds. + + Recompute + + ); + } + + if (htmlEvent.executionStatus == 'in_progress') { + return ( + +

Computing…

+
+ ); + } + + return null; +} + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-top: 2rem; +`; + +const StyledContent = styled.p` + font-size: 0.8rem; + margin-bottom: 0; +`; diff --git a/apps/squareone/src/components/TimesSquareGitHubPagePanel/ExecStats.stories.js b/apps/squareone/src/components/TimesSquareGitHubPagePanel/ExecStats.stories.js new file mode 100644 index 00000000..00b6a35c --- /dev/null +++ b/apps/squareone/src/components/TimesSquareGitHubPagePanel/ExecStats.stories.js @@ -0,0 +1,56 @@ +import React from 'react'; + +import { TimesSquareHtmlEventsContext } from '../TimesSquareHtmlEventsProvider'; +import ExecStats from './ExecStats'; + +export default { + component: ExecStats, + title: 'Components/TimesSquare/ExecStats', + parameters: { + viewport: { + viewports: { + sidebar: { + name: 'Sidebar', + styles: { + width: '280px', + height: '900px', + }, + }, + }, + }, + defaultViewport: 'sidebar', + }, +}; + +const Template = (args) => ( + + + +); + +export const Default = Template.bind({}); +Default.args = { + dateSubmitted: '2021-09-01T12:00:00Z', + dateStarted: '2021-09-01T12:00:01Z', + dateFinished: '2021-09-01T12:00:10Z', + executionStatus: 'complete', + executionDuration: 10.12, +}; + +export const InProgressNew = Template.bind({}); +InProgressNew.args = { + dateSubmitted: '2021-09-01T12:00:10Z', + dateStarted: null, + dateFinished: null, + executionStatus: 'in_progress', + executionDuration: null, +}; + +export const InProgressExisting = Template.bind({}); +InProgressExisting.args = { + dateSubmitted: '2021-09-01T12:00:00Z', + dateStarted: '2021-09-01T12:00:01Z', + dateFinished: '2021-09-01T12:00:10Z', + executionStatus: 'in_progress', + executionDuration: 10.12, +}; diff --git a/apps/squareone/src/components/TimesSquareGitHubPagePanel/TimesSquareGitHubPagePanel.js b/apps/squareone/src/components/TimesSquareGitHubPagePanel/TimesSquareGitHubPagePanel.js index 41af1b04..d8ec2a6e 100644 --- a/apps/squareone/src/components/TimesSquareGitHubPagePanel/TimesSquareGitHubPagePanel.js +++ b/apps/squareone/src/components/TimesSquareGitHubPagePanel/TimesSquareGitHubPagePanel.js @@ -4,6 +4,7 @@ * the notebook content (NotebookIframe). */ +import React from 'react'; import styled from 'styled-components'; import getConfig from 'next/config'; import Head from 'next/head'; @@ -11,14 +12,11 @@ import Error from 'next/error'; import useTimesSquarePage from '../../hooks/useTimesSquarePage'; import TimesSquareParameters from '../TimesSquareParameters'; +import ExecStats from './ExecStats'; -export default function TimesSquareGitHubPagePanel({ - tsPageUrl, - userParameters, - displaySettings, -}) { +export default function TimesSquareGitHubPagePanel({}) { const { publicRuntimeConfig } = getConfig(); - const pageData = useTimesSquarePage(tsPageUrl); + const pageData = useTimesSquarePage(); if (pageData.loading) { return

Loading...

; @@ -39,11 +37,9 @@ export default function TimesSquareGitHubPagePanel({ {description && (
)} - + + + ); diff --git a/apps/squareone/src/components/TimesSquareHtmlEventsProvider/TimesSquareHtmlEventsProvider.js b/apps/squareone/src/components/TimesSquareHtmlEventsProvider/TimesSquareHtmlEventsProvider.js new file mode 100644 index 00000000..62e2fb0f --- /dev/null +++ b/apps/squareone/src/components/TimesSquareHtmlEventsProvider/TimesSquareHtmlEventsProvider.js @@ -0,0 +1,70 @@ +/* + * Context provider for the Times Square /pages/:page/html/events endpoint. + */ + +import React from 'react'; +import { fetchEventSource } from '@microsoft/fetch-event-source'; + +import useTimesSquarePage from '../../hooks/useTimesSquarePage'; +import { TimesSquareUrlParametersContext } from '../TimesSquareUrlParametersProvider'; + +export const TimesSquareHtmlEventsContext = React.createContext(); + +export default function TimesSquareHtmlEventsProvider({ children }) { + const [htmlEvent, setHtmlEvent] = React.useState(null); + + const urlParameters = React.useContext(TimesSquareUrlParametersContext); + const timesSquarePage = useTimesSquarePage(); + + const urlQueryString = urlParameters.urlQueryString; + const htmlEventsUrl = timesSquarePage.htmlEventsUrl; + const fullHtmlEventsUrl = htmlEventsUrl + ? `${htmlEventsUrl}?${urlQueryString}` + : null; + + React.useEffect(() => { + const abortController = new AbortController(); + + async function runEffect() { + if (htmlEventsUrl) { + await fetchEventSource(fullHtmlEventsUrl, { + method: 'GET', + signal: abortController.signal, + onopen(res) { + if (res.status >= 400 && res.status < 500 && res.status !== 429) { + console.log(`Client side error ${fullHtmlEventsUrl}`, res); + } + }, + onmessage(event) { + const parsedData = JSON.parse(event.data); + setHtmlEvent(parsedData); + }, + onclose() {}, + onerror(err) {}, + }); + } + } + runEffect(); + + return () => { + // Clean up: close the event source connection + abortController.abort(); + }; + }, [fullHtmlEventsUrl, htmlEventsUrl]); + + const contextValue = { + dateSubmitted: htmlEvent ? htmlEvent.date_submitted : null, + dateStarted: htmlEvent ? htmlEvent.date_started : null, + dateFinished: htmlEvent ? htmlEvent.date_finished : null, + executionStatus: htmlEvent ? htmlEvent.execution_status : null, + executionDuration: htmlEvent ? htmlEvent.execution_duration : null, + htmlHash: htmlEvent ? htmlEvent.html_hash : null, + htmlUrl: htmlEvent ? htmlEvent.html_url : null, + }; + + return ( + + {children} + + ); +} diff --git a/apps/squareone/src/components/TimesSquareHtmlEventsProvider/index.js b/apps/squareone/src/components/TimesSquareHtmlEventsProvider/index.js new file mode 100644 index 00000000..57a3b083 --- /dev/null +++ b/apps/squareone/src/components/TimesSquareHtmlEventsProvider/index.js @@ -0,0 +1,2 @@ +export * from './TimesSquareHtmlEventsProvider'; +export { default } from './TimesSquareHtmlEventsProvider'; diff --git a/apps/squareone/src/components/TimesSquareNotebookViewer/TimesSquareNotebookViewer.js b/apps/squareone/src/components/TimesSquareNotebookViewer/TimesSquareNotebookViewer.js index 1a72f191..60327444 100644 --- a/apps/squareone/src/components/TimesSquareNotebookViewer/TimesSquareNotebookViewer.js +++ b/apps/squareone/src/components/TimesSquareNotebookViewer/TimesSquareNotebookViewer.js @@ -3,9 +3,11 @@ * from Times Square with a notebook render. */ +import React from 'react'; import styled from 'styled-components'; import useHtmlStatus from './useHtmlStatus'; +import { TimesSquareUrlParametersContext } from '../TimesSquareUrlParametersProvider'; const StyledIframe = styled.iframe` /* --shadow-color: 0deg 0% 74%; @@ -19,12 +21,9 @@ const StyledIframe = styled.iframe` height: 100%; `; -export default function TimesSquareNotebookViewer({ - tsPageUrl, - parameters, - displaySettings, -}) { - const htmlStatus = useHtmlStatus(tsPageUrl, parameters, displaySettings); +export default function TimesSquareNotebookViewer({}) { + const { tsPageUrl } = React.useContext(TimesSquareUrlParametersContext); + const htmlStatus = useHtmlStatus(); if (htmlStatus.error) { return ( diff --git a/apps/squareone/src/components/TimesSquareNotebookViewer/useHtmlStatus.js b/apps/squareone/src/components/TimesSquareNotebookViewer/useHtmlStatus.js index 53d3b976..c811aa75 100644 --- a/apps/squareone/src/components/TimesSquareNotebookViewer/useHtmlStatus.js +++ b/apps/squareone/src/components/TimesSquareNotebookViewer/useHtmlStatus.js @@ -4,8 +4,10 @@ * dynamic refreshing of data about a page's HTML rendering. */ +import React from 'react'; import useSWR from 'swr'; import useTimesSquarePage from '../../hooks/useTimesSquarePage'; +import { TimesSquareUrlParametersContext } from '../TimesSquareUrlParametersProvider'; const fetcher = (...args) => fetch(...args).then((res) => res.json()); @@ -20,8 +22,11 @@ export function parameterizeUrl(baseUrl, parameters, displaySettings) { return url.toString(); } -function useHtmlStatus(pageUrl, parameters, displaySettings) { - const pageData = useTimesSquarePage(pageUrl); +function useHtmlStatus() { + const { notebookParameters: parameters, displaySettings } = React.useContext( + TimesSquareUrlParametersContext + ); + const pageData = useTimesSquarePage(); const { data, error } = useSWR( () => parameterizeUrl(pageData.htmlStatusUrl, parameters, displaySettings), diff --git a/apps/squareone/src/components/TimesSquareParameters/TimesSquareParameters.js b/apps/squareone/src/components/TimesSquareParameters/TimesSquareParameters.js index 6ae3d36a..70c8843b 100644 --- a/apps/squareone/src/components/TimesSquareParameters/TimesSquareParameters.js +++ b/apps/squareone/src/components/TimesSquareParameters/TimesSquareParameters.js @@ -1,3 +1,4 @@ +import React from 'react'; import { useRouter } from 'next/router'; import styled from 'styled-components'; import { Formik, Field } from 'formik'; @@ -7,6 +8,9 @@ import Button, { RedGhostButton } from '../Button'; import StringInput from './StringInput'; import ParameterInput from './ParameterInput'; +import { TimesSquareUrlParametersContext } from '../TimesSquareUrlParametersProvider'; +import useTimesSquarePage from '../../hooks/useTimesSquarePage'; + // Create input components based on the parameter's JSON schema function inputFactory(props) { const { paramName, paramSchema, value, onChange, errors, touched } = props; @@ -28,13 +32,13 @@ function inputFactory(props) { ); } -export default function TimesSquareParameters({ - pageData, - userParameters, - displaySettings, -}) { +export default function TimesSquareParameters({}) { const router = useRouter(); - const { parameters } = pageData; + + const { displaySettings, notebookParameters: userParameters } = + React.useContext(TimesSquareUrlParametersContext); + const { parameters } = useTimesSquarePage(); + const ajv = new Ajv({ coerceTypes: true }); // Merge userParameters with defaults @@ -100,6 +104,7 @@ export default function TimesSquareParameters({ return ( (
@@ -136,13 +142,13 @@ export default function TimesSquareParameters({ - Reset diff --git a/apps/squareone/src/components/TimesSquareUrlParametersProvider/TimesSquareUrlParametersProvider.js b/apps/squareone/src/components/TimesSquareUrlParametersProvider/TimesSquareUrlParametersProvider.js new file mode 100644 index 00000000..d2059f5a --- /dev/null +++ b/apps/squareone/src/components/TimesSquareUrlParametersProvider/TimesSquareUrlParametersProvider.js @@ -0,0 +1,71 @@ +/* + * Context provider for the current page's notebook and display parameters + * that come from the URL path and query parameters. + */ + +import React from 'react'; +import getConfig from 'next/config'; +import { useRouter } from 'next/router'; + +export const TimesSquareUrlParametersContext = React.createContext(); + +export default function TimesSquareUrlParametersProvider({ children }) { + const { publicRuntimeConfig } = getConfig(); + const { timesSquareUrl } = publicRuntimeConfig; + const router = useRouter(); + + // Get components out of the URL path. Only /github-pr/ pages have owner, + // repo, and commit components in the path. In /github/ pages the owner + // and repo are part of tsSlug. + const { tsSlug, owner = null, repo = null, commit = null } = router.query; + + // Since the page's path is a [...tsSlug], we need to join the parts of the + // path to get the full slug. This combines the owner, repo, directory, and + // notebook name for regular /github/ pages, or just the directory and + // notebook name for /github-pr/ pages. + const githubSlug = tsSlug ? tsSlug.join('/') : null; + + // Construct the URL for the Times Square API endpoint that gives information + // about the page. GitHub PR pages (github-pr) have different API URLs than + // regular GitHub pages. + const tsPageUrl = router.pathname.startsWith('/times-square/github-pr') + ? `${timesSquareUrl}/v1/github-pr/${owner}/${repo}/${commit}/${githubSlug}` + : `${timesSquareUrl}/v1/github/${githubSlug}`; + + // Get the user query parameters from the URL. In next 13 we can use the + // useSearchParams hook (https://nextjs.org/docs/app/api-reference/functions/use-search-params) + // to get these directly, but for now we have to filter them out of the + // router.query object. Even if path components leak in, we can still filter + // them out in the UI for setting parameters because we only show parameters + // matching the parameter schema. + const userParameters = Object.fromEntries( + Object.entries(router.query) + .filter((item) => item[0] != 'tsSlug') + .map((item) => item) + ); + + const queryString = new URLSearchParams(userParameters).toString(); + + // pop display settings from the user parameters and to also separate out + // the notebook parameters. + const { ts_hide_code = '1', ...notebookParameters } = userParameters; + const displaySettings = { ts_hide_code }; + + return ( + + {children} + + ); +} diff --git a/apps/squareone/src/components/TimesSquareUrlParametersProvider/index.js b/apps/squareone/src/components/TimesSquareUrlParametersProvider/index.js new file mode 100644 index 00000000..514281f4 --- /dev/null +++ b/apps/squareone/src/components/TimesSquareUrlParametersProvider/index.js @@ -0,0 +1,2 @@ +export * from './TimesSquareUrlParametersProvider'; +export { default } from './TimesSquareUrlParametersProvider'; diff --git a/apps/squareone/src/hooks/useTimesSquarePage.js b/apps/squareone/src/hooks/useTimesSquarePage.js index 47e51c07..fb203aea 100644 --- a/apps/squareone/src/hooks/useTimesSquarePage.js +++ b/apps/squareone/src/hooks/useTimesSquarePage.js @@ -1,9 +1,13 @@ +import React from 'react'; import useSWR from 'swr'; +import { TimesSquareUrlParametersContext } from '../components/TimesSquareUrlParametersProvider'; + const fetcher = (...args) => fetch(...args).then((res) => res.json()); -function useTimesSquarePage(pageUrl) { - const { data, error } = useSWR(pageUrl, fetcher); +function useTimesSquarePage() { + const { tsPageUrl } = React.useContext(TimesSquareUrlParametersContext); + const { data, error } = useSWR(tsPageUrl, fetcher); return { error: error, @@ -13,6 +17,7 @@ function useTimesSquarePage(pageUrl) { description: data ? data.description : null, htmlUrl: data ? data.html_url : null, htmlStatusUrl: data ? data.html_status_url : null, + htmlEventsUrl: data ? data.html_events_url : null, }; } diff --git a/apps/squareone/src/pages/times-square/github-pr/[owner]/[repo]/[commit]/[...tsSlug].js b/apps/squareone/src/pages/times-square/github-pr/[owner]/[repo]/[commit]/[...tsSlug].js index b99b310c..11e5cf15 100644 --- a/apps/squareone/src/pages/times-square/github-pr/[owner]/[repo]/[commit]/[...tsSlug].js +++ b/apps/squareone/src/pages/times-square/github-pr/[owner]/[repo]/[commit]/[...tsSlug].js @@ -1,54 +1,20 @@ import getConfig from 'next/config'; -import { useRouter } from 'next/router'; import TimesSquareApp from '../../../../../../components/TimesSquareApp'; import WideContentLayout from '../../../../../../components/WideContentLayout'; -import TimesSquarePrGitHubNav from '../../../../../../components/TimesSquarePrGitHubNav'; import TimesSquareNotebookViewer from '../../../../../../components/TimesSquareNotebookViewer'; -import TimesSquareGitHubPagePanel from '../../../../../../components/TimesSquareGitHubPagePanel/TimesSquareGitHubPagePanel'; - -export function TimesSquareGitHubPrNav({ pagePath }) { - return
Nav component
; -} +import TimesSquareUrlParametersProvider from '../../../../../../components/TimesSquareUrlParametersProvider'; +import TimesSquareHtmlEventsProvider from '../../../../../../components/TimesSquareHtmlEventsProvider/TimesSquareHtmlEventsProvider'; export default function GitHubPrNotebookViewPage({}) { - const { publicRuntimeConfig } = getConfig(); - const { timesSquareUrl } = publicRuntimeConfig; - const router = useRouter(); - const { owner, repo, commit, tsSlug } = router.query; - const tsPageUrl = `${timesSquareUrl}/v1/github-pr/${owner}/${repo}/${commit}/${tsSlug.join( - '/' - )}`; - - const userParameters = Object.fromEntries( - Object.entries(router.query) - .filter((item) => item[0] != 'tsSlug') - .map((item) => item) - ); - - const { ts_hide_code = '1' } = userParameters; - const displaySettings = { ts_hide_code }; - - const pageNav = ( - - ); - - const pagePanel = ( - - ); - return ( - - - + + + + + + + ); } diff --git a/apps/squareone/src/pages/times-square/github/[...tsSlug].js b/apps/squareone/src/pages/times-square/github/[...tsSlug].js index 7c5a1b99..9aa77f79 100644 --- a/apps/squareone/src/pages/times-square/github/[...tsSlug].js +++ b/apps/squareone/src/pages/times-square/github/[...tsSlug].js @@ -1,47 +1,20 @@ import getConfig from 'next/config'; -import { useRouter } from 'next/router'; import TimesSquareApp from '../../../components/TimesSquareApp'; import WideContentLayout from '../../../components/WideContentLayout'; -import TimesSquareMainGitHubNav from '../../../components/TimesSquareMainGitHubNav'; import TimesSquareNotebookViewer from '../../../components/TimesSquareNotebookViewer'; -import TimesSquareGitHubPagePanel from '../../../components/TimesSquareGitHubPagePanel/TimesSquareGitHubPagePanel'; +import TimesSquareUrlParametersProvider from '../../../components/TimesSquareUrlParametersProvider'; +import TimesSquareHtmlEventsProvider from '../../../components/TimesSquareHtmlEventsProvider/TimesSquareHtmlEventsProvider'; export default function GitHubNotebookViewPage({}) { - const { publicRuntimeConfig } = getConfig(); - const { timesSquareUrl } = publicRuntimeConfig; - const router = useRouter(); - const { tsSlug } = router.query; - const githubSlug = tsSlug.join('/'); - const tsPageUrl = `${timesSquareUrl}/v1/github/${githubSlug}`; - - const userParameters = Object.fromEntries( - Object.entries(router.query) - .filter((item) => item[0] != 'tsSlug') - .map((item) => item) - ); - - const { ts_hide_code = '1' } = userParameters; - const displaySettings = { ts_hide_code }; - - const pageNav = ; - - const pagePanel = ( - - ); - return ( - - - + + + + + + + ); } @@ -50,7 +23,7 @@ GitHubNotebookViewPage.getLayout = function getLayout(page) { }; export async function getServerSideProps() { - const { serverRuntimeConfig, publicRuntimeConfig } = getConfig(); + const { publicRuntimeConfig } = getConfig(); // Make the page return a 404 if Times Square is not configured const notFound = publicRuntimeConfig.timesSquareUrl ? false : true; diff --git a/apps/squareone/src/pages/times-square/index.js b/apps/squareone/src/pages/times-square/index.js index 7e9de7e0..972ba784 100644 --- a/apps/squareone/src/pages/times-square/index.js +++ b/apps/squareone/src/pages/times-square/index.js @@ -2,108 +2,110 @@ import Head from 'next/head'; import getConfig from 'next/config'; import PropTypes from 'prop-types'; -import TimesSquareMainGitHubNav from '../../components/TimesSquareMainGitHubNav'; import TimesSquareApp from '../../components/TimesSquareApp'; import WideContentLayout from '../../components/WideContentLayout'; +import TimesSquareUrlParametersProvider from '../../components/TimesSquareUrlParametersProvider'; export default function TimesSquareHome({ publicRuntimeConfig }) { - const pageNav = ; - return ( - - - Times Square | {publicRuntimeConfig.siteName} - -

Easily share Jupyter Notebooks on the Rubin Science Platform

-

- Times Square lets you view computed Jupyter Notebooks in your browser, - without spinning up a JupyterLab server. To share a notebook, simply - copy a URL. -

-

- All Times Square pages are computed on the Rubin Science Platform. Any - software (like the LSST Science Pipelines) or data source (such as the - Butler and EFD) available in interactive JupyterLab sessions are - available to Times Square notebooks. -

-

- Times Square is great for sharing engineering and observatory reports, - dashboards, and analyses. -

+ + + + Times Square | {publicRuntimeConfig.siteName} + +

Easily share Jupyter Notebooks on the Rubin Science Platform

+

+ Times Square lets you view computed Jupyter Notebooks in your browser, + without spinning up a JupyterLab server. To share a notebook, simply + copy a URL. +

+

+ All Times Square pages are computed on the Rubin Science Platform. Any + software (like the LSST Science Pipelines) or data source (such as the + Butler and EFD) available in interactive JupyterLab sessions are + available to Times Square notebooks. +

+

+ Times Square is great for sharing engineering and observatory reports, + dashboards, and analyses. +

-

Edit on GitHub...

-

- You can collaboratively maintain collections of notebooks in GitHub - repositories. When you push to the default branch of a repository where - the Times Square’s GitHub App is installed, Times Square syncs and - computes those notebooks on the Rubin Science Platform. Check out{' '} - - lsst-sqre/times-square-demo - {' '} - for an example. -

-

... or upload a notebook directly (coming soon)

-

- Are you working on a notebook and not ready to commit it to GitHub yet - (or ever)? You can directly upload a notebook to your personal - collection of fliers. These are still published and you can - share links to your fliers. Just keep in mind that any notebook that the - team relies on and will use in the long term should get moved over to - GitHub for better collaboration and maintainability. -

-

Tweak parameters on the fly

-

- You can use Jinja2 syntax in Markdown and code cells to{' '} - parameterize Jupyter Notebooks with user inputs. - Parameterizations are great for changing the date in a nightly report, - or changing the input dataset for a basic data analysis. You, and other - users, can change those parameters and see the notebook recomputed - on-the-fly. Notebook parameterizations are shareable: just copy the URL. -

-

In beta

-

- Times Square is currently in beta, meaning that we’re still implementing - basic features and writing documentation. There’s also a chance that we - might need to reset databases. You can try Times Square out, but don’t - rely on it for critical applications just yet. -

-

- Reach out to us on Slack at{' '} - #dm-square if - you have questions. -

-

Behind the scenes

-

The Times Square app is built largely from three components:

-
    -
  • - squareone{' '} - contains the user interface -
  • -
  • - times-square{' '} - is the API service that manages notebooks -
  • -
  • - noteburst is the - API service that runs Jupyter Notebooks on the Rubin Science Platform. -
  • -
-

These documents cover Times Square’s architecture:

- -
+

Edit on GitHub...

+

+ You can collaboratively maintain collections of notebooks in GitHub + repositories. When you push to the default branch of a repository + where the Times Square’s GitHub App is installed, Times Square syncs + and computes those notebooks on the Rubin Science Platform. Check out{' '} + + lsst-sqre/times-square-demo + {' '} + for an example. +

+

... or upload a notebook directly (coming soon)

+

+ Are you working on a notebook and not ready to commit it to GitHub yet + (or ever)? You can directly upload a notebook to your personal + collection of fliers. These are still published and you can + share links to your fliers. Just keep in mind that any notebook that + the team relies on and will use in the long term should get moved over + to GitHub for better collaboration and maintainability. +

+

Tweak parameters on the fly

+

+ You can use Jinja2 syntax in Markdown and code cells to{' '} + parameterize Jupyter Notebooks with user inputs. + Parameterizations are great for changing the date in a nightly report, + or changing the input dataset for a basic data analysis. You, and + other users, can change those parameters and see the notebook + recomputed on-the-fly. Notebook parameterizations are shareable: just + copy the URL. +

+

In beta

+

+ Times Square is currently in beta, meaning that we’re still + implementing basic features and writing documentation. There’s also a + chance that we might need to reset databases. You can try Times Square + out, but don’t rely on it for critical applications just yet. +

+

+ Reach out to us on Slack at{' '} + #dm-square if + you have questions. +

+

Behind the scenes

+

The Times Square app is built largely from three components:

+
    +
  • + squareone{' '} + contains the user interface +
  • +
  • + times-square{' '} + is the API service that manages notebooks +
  • +
  • + noteburst is + the API service that runs Jupyter Notebooks on the Rubin Science + Platform. +
  • +
+

These documents cover Times Square’s architecture:

+ +
+ ); } diff --git a/packages/squared/package.json b/packages/squared/package.json index 8b49c8e1..e9476bae 100644 --- a/packages/squared/package.json +++ b/packages/squared/package.json @@ -45,7 +45,7 @@ "@storybook/addon-onboarding": "^1.0.8", "@storybook/addon-styling": "^1.3.5", "@storybook/blocks": "^7.2.1", - "@storybook/jest": "^0.2.0", + "@storybook/jest": "^0.2.3", "@storybook/react": "^7.2.1", "@storybook/react-vite": "^7.2.1", "@storybook/test-runner": "^0.13.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05dbcdc0..0fc7ff48 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: '@lsst-sqre/squared': specifier: workspace:* version: link:../../packages/squared + '@microsoft/fetch-event-source': + specifier: ^2.0.1 + version: 2.0.1 '@reach/alert': specifier: ^0.17.0 version: 0.17.0(react-dom@17.0.2)(react@17.0.2) @@ -65,6 +68,9 @@ importers: ajv: specifier: ^8.11.0 version: 8.11.0 + date-fns: + specifier: ^3.6.0 + version: 3.6.0 formik: specifier: ^2.2.9 version: 2.2.9(react@17.0.2) @@ -264,8 +270,8 @@ importers: specifier: ^7.2.1 version: 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.19)(react-dom@17.0.2)(react@17.0.2) '@storybook/jest': - specifier: ^0.2.0 - version: 0.2.0 + specifier: ^0.2.3 + version: 0.2.3(jest@28.1.3) '@storybook/react': specifier: ^7.2.1 version: 7.2.1(react-dom@17.0.2)(react@17.0.2)(typescript@5.1.6) @@ -330,6 +336,10 @@ packages: resolution: {integrity: sha512-+RNNcQvw2V1bmnBTPAtOLfW/9mhH2vC67+rUSi5T8EtEWt6lEnGNY2GuhZ1/YwbgikT1TkhvidCDmN5Q5YCo/w==} dev: true + /@adobe/css-tools@4.3.3: + resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} + dev: true + /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} @@ -4094,6 +4104,10 @@ packages: '@types/react': 18.2.19 react: 17.0.2 + /@microsoft/fetch-event-source@2.0.1: + resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==} + dev: false + /@mswjs/cookies@0.2.2: resolution: {integrity: sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==} engines: {node: '>=14'} @@ -6596,8 +6610,8 @@ packages: - supports-color dev: true - /@storybook/expect@27.5.2-0: - resolution: {integrity: sha512-cP99mhWN/JeCp7VSIiymvj5tmuMY050iFohvp8Zq+kewKsBSZ6/qpTJAGCCZk6pneTcp4S0Fm5BSqyxzbyJ3gw==} + /@storybook/expect@28.1.3-5: + resolution: {integrity: sha512-lS1oJnY1qTAxnH87C765NdfvGhksA6hBcbUVI5CHiSbNsEtr456wtg/z+dT9XlPriq1D5t2SgfNL9dBAoIGyIA==} dependencies: '@types/jest': 28.1.3 dev: true @@ -6637,13 +6651,18 @@ packages: '@storybook/preview-api': 7.2.1 dev: true - /@storybook/jest@0.2.0: - resolution: {integrity: sha512-z0S+tMWEfV5rK4drIaPaDcOdnJ02Rs/A8gfLnrZXrlYZiDWC7CvPIhpJDwBYnR6MZ3udn8lOD8V/fbTbSEV7Rg==} + /@storybook/jest@0.2.3(jest@28.1.3): + resolution: {integrity: sha512-ov5izrmbAFObzKeh9AOC5MlmFxAcf0o5i6YFGae9sDx6DGh6alXsRM+chIbucVkUwVHVlSzdfbLDEFGY/ShaYw==} dependencies: - '@storybook/expect': 27.5.2-0 - '@testing-library/jest-dom': 5.17.0 + '@storybook/expect': 28.1.3-5 + '@testing-library/jest-dom': 6.4.2(@types/jest@28.1.3)(jest@28.1.3) '@types/jest': 28.1.3 jest-mock: 27.5.1 + transitivePeerDependencies: + - '@jest/globals' + - '@types/bun' + - jest + - vitest dev: true /@storybook/manager-api@7.1.1(react-dom@17.0.2)(react@17.0.2): @@ -7363,17 +7382,35 @@ packages: pretty-format: 27.5.1 dev: true - /@testing-library/jest-dom@5.17.0: - resolution: {integrity: sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==} - engines: {node: '>=8', npm: '>=6', yarn: '>=1'} + /@testing-library/jest-dom@6.4.2(@types/jest@28.1.3)(jest@28.1.3): + resolution: {integrity: sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + peerDependencies: + '@jest/globals': '>= 28' + '@types/bun': latest + '@types/jest': '>= 28' + jest: '>= 28' + vitest: '>= 0.32' + peerDependenciesMeta: + '@jest/globals': + optional: true + '@types/bun': + optional: true + '@types/jest': + optional: true + jest: + optional: true + vitest: + optional: true dependencies: - '@adobe/css-tools': 4.3.0 + '@adobe/css-tools': 4.3.3 '@babel/runtime': 7.22.6 - '@types/testing-library__jest-dom': 5.14.9 + '@types/jest': 28.1.3 aria-query: 5.3.0 chalk: 3.0.0 css.escape: 1.5.1 - dom-accessibility-api: 0.5.16 + dom-accessibility-api: 0.6.3 + jest: 28.1.3(@types/node@20.5.0) lodash: 4.17.21 redent: 3.0.0 dev: true @@ -7833,12 +7870,6 @@ packages: csstype: 3.1.2 dev: true - /@types/testing-library__jest-dom@5.14.9: - resolution: {integrity: sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==} - dependencies: - '@types/jest': 28.1.3 - dev: true - /@types/through@0.0.30: resolution: {integrity: sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==} dependencies: @@ -9932,6 +9963,10 @@ packages: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} dev: true + /date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + dev: false + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -10226,6 +10261,10 @@ packages: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} dev: true + /dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dev: true + /dom-converter@0.2.0: resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} dependencies: @@ -13286,7 +13325,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 20.4.5 + '@types/node': 20.5.0 dev: true /jest-mock@28.1.3: