Skip to content

Commit

Permalink
Merge pull request #1215 from jay-hodgson/PORTALS-3241
Browse files Browse the repository at this point in the history
  • Loading branch information
jay-hodgson authored Sep 19, 2024
2 parents 02ec6b6 + 1430200 commit 078eaf6
Show file tree
Hide file tree
Showing 15 changed files with 198 additions and 4 deletions.
3 changes: 2 additions & 1 deletion apps/portals/nf/src/config/resources.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export const datasetsSql = 'SELECT * FROM syn50913342'
export const publicationsSql = 'SELECT * FROM syn16857542'
export const studiesSql = 'SELECT * FROM syn52694652'
export const initiativesSql = 'SELECT * FROM syn24189696 order by initiative asc'
export const initiativesSql =
'SELECT * FROM syn24189696 order by initiative asc'
export const toolsSql = 'SELECT * FROM syn51730943'
export const peopleSql = 'SELECT * FROM syn23564971'
export const filesSql = `SELECT * FROM syn52702673 WHERE resourceType in ('experimentalData', 'results', 'analysis')`
Expand Down
2 changes: 2 additions & 0 deletions apps/portals/nf/src/config/routesConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { GenericRoute } from '@sage-bionetworks/synapse-portal-framework/types/portal-config'
import { SharePageLinkButtonConfig } from '@sage-bionetworks/synapse-portal-framework/src/shared-config/SharePageLinkButtonConfig'
import { SynapseConstants } from 'synapse-react-client'
import {
newStudiesSql,
Expand Down Expand Up @@ -265,6 +266,7 @@ const routes: GenericRoute[] = [
{
path: '',
synapseConfigArray: [
SharePageLinkButtonConfig,
{
name: 'CardContainerLogic',
isOutsideContainer: true,
Expand Down
2 changes: 2 additions & 0 deletions apps/portals/nf/src/config/synapseConfigs/tools.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { CardConfiguration } from 'synapse-react-client'
import { GenericCardSchema, SynapseConstants } from 'synapse-react-client'
import { SynapseConfig } from '@sage-bionetworks/synapse-portal-framework/types/portal-config'
import { SharePageLinkButtonConfig } from '@sage-bionetworks/synapse-portal-framework/src/shared-config/SharePageLinkButtonConfig'
import { columnAliases } from './commonProps'
import {
catalogNumberSql,
Expand Down Expand Up @@ -358,6 +359,7 @@ export const toolDetailsPageConfig: DetailsPageProps = {
}

export const toolsDetailsPage: SynapseConfig[] = [
SharePageLinkButtonConfig,
{
name: 'CardContainerLogic',
isOutsideContainer: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { SynapseConfig } from '../types/portal-config'

export const SharePageLinkButtonConfig: SynapseConfig = {
name: 'SharePageLinkButton',
props: {
shortIoPublicApiKey: 'pk_y4sPMLrxonM7kNQV',
buttonProps: {
variant: 'text',
color: 'light',
sx: {
position: 'absolute',
top: '50px',
right: '20px',
zIndex: 100,
},
},
},
containerClassName: 'container-full-width',
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ $svg-icon-height: 30px;

.DetailsPage {
display: flex;

// keeps h2 in markdown and in this component the same
.h2,
h2 {
Expand Down
6 changes: 6 additions & 0 deletions apps/synapse-portal-framework/src/types/portal-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
UserCardListRotateProps,
UserCardProps,
DynamicFormProps,
SharePageLinkButtonProps,
} from 'synapse-react-client'
import { RouteControlWrapperProps } from '../components/RouteControlWrapper'
import { HomePageCardContainerProps } from '../components/csbc-home-page/HomePageCardContainer'
Expand Down Expand Up @@ -177,6 +178,10 @@ type GenieHomePageHeader = {
name: 'GenieHomePageHeader'
props: undefined
}
type SharePageLinkButton = {
name: 'SharePageLinkButton'
props: SharePageLinkButtonProps
}
type SynapseComponentCollapse = {
name: 'SynapseComponentCollapse'
props: SynapseComponentCollapseProps
Expand Down Expand Up @@ -388,6 +393,7 @@ export type SynapseConfig = (
| TimelinePlot
| DatasetJsonLdScript
| DynamicForm
| SharePageLinkButton
) &
Metadata

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Meta, StoryObj } from '@storybook/react'
import SharePageLinkButton from './SharePageLinkButton'

const meta = {
title: 'UI/SharePageLinkButton',
component: SharePageLinkButton,
} satisfies Meta
export default meta
type Story = StoryObj<typeof meta>

export const SharePageLinkButtonStory: Story = {
args: {
buttonProps: {
sx: { position: 'fixed', right: '20px' },
},
},
parameters: {
stack: 'mock',
msw: {
handlers: [],
},
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import { createWrapper } from '../../testutils/TestingLibraryUtils'
import SharePageLinkButton, {
SharePageLinkButtonProps,
} from './SharePageLinkButton'
import { MOCK_SHORT_IO_URL } from '../../mocks/mockShortIo'
import { server } from '../../mocks/msw/server'

function renderComponent(props: SharePageLinkButtonProps) {
return render(<SharePageLinkButton {...props} />, {
wrapper: createWrapper(),
})
}
describe('SharePageLinkButton', () => {
beforeAll(() => server.listen())
afterEach(() => server.restoreHandlers())
afterAll(() => server.close())
beforeEach(() => {
jest.clearAllMocks()
// Replace clipboard.writeText with a mock
Object.assign(navigator, {
clipboard: {
writeText: jest.fn().mockImplementation(() => Promise.resolve()),
},
})
})

it('Copies short.io response to clipboard', async () => {
renderComponent({ shortIoPublicApiKey: 'abc' })
expect(screen.queryByRole('alert')).not.toBeInTheDocument()

await userEvent.click(
screen.getByRole('button', { name: 'Share Page Link' }),
)
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
MOCK_SHORT_IO_URL,
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useCallback } from 'react'
import { useMutation } from '@tanstack/react-query'
import { displayToast } from '../ToastMessage'
import IconSvg from '../IconSvg'
import { Button, ButtonProps } from '@mui/material'

export type SharePageLinkButtonProps = {
shortIoPublicApiKey?: string
domain?: string
buttonProps?: ButtonProps
}

export const SharePageLinkButton: React.FunctionComponent<
SharePageLinkButtonProps
> = ({ shortIoPublicApiKey, domain = 'sageb.io', buttonProps }) => {
const copyToClipboard = useCallback((value: string) => {
navigator.clipboard.writeText(value).then(() => {
displayToast('Page URL copied to the clipboard', 'success')
})
}, [])
// create short io link (if not already created)
const { mutate: createShortUrl } = useMutation({
mutationFn: async () => {
if (!shortIoPublicApiKey) {
return window.location.href
} else {
const response = await fetch('https://api.short.io/links/public', {
method: 'POST',
headers: {
Authorization: shortIoPublicApiKey,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
originalURL: window.location.href,
domain: domain,
}),
})
if (!response.ok) {
const responseText = await response.text()
throw new Error(responseText)
}
const jsonResponse = await response.json()
return jsonResponse.shortURL
}
},
onSuccess: data => {
copyToClipboard(data)
},
onError: error => {
console.error(error)
copyToClipboard(window.location.href)
},
})

return (
<Button
variant="contained"
onClick={() => {
createShortUrl()
}}
{...buttonProps}
startIcon={<IconSvg icon="contentCopy" wrap={false} />}
>
Share Page Link
</Button>
)
}

export default SharePageLinkButton
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import SharePageLinkButton from './SharePageLinkButton'
import type { SharePageLinkButtonProps } from './SharePageLinkButton'
export { SharePageLinkButton, SharePageLinkButtonProps }
export default SharePageLinkButton
Original file line number Diff line number Diff line change
Expand Up @@ -499,8 +499,8 @@ export class SynapseFormSubmissionGrid extends React.Component<
title="More Information"
content={
<>
Please <Link href="mailto:ModelAD@iu.edu">contact us</Link>{' '}
for more information about your submission
Please <Link href="mailto:ModelAD@iu.edu">contact us</Link> for
more information about your submission
</>
}
className={`theme-${this.props.formClass}`}
Expand Down
1 change: 1 addition & 0 deletions packages/synapse-react-client/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export * from './AccessRequirementRelatedProjectsList'
export * from './HelpPopover'
export * from './MuiContainer'
export * from './DatasetJsonLdScript'
export * from './SharePageLinkButton'
export * from './SageResourcesPopover'

// TODO: Find a better way to expose Icon components
Expand Down
12 changes: 12 additions & 0 deletions packages/synapse-react-client/src/mocks/mockShortIo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const MOCK_SHORT_IO_URL = 'https://short.io/abc123'
export const mockShortIoResponse: any = (
originalURL: string,
domain: string,
) => {
return {
id: '123456',
originalURL,
shortURL: MOCK_SHORT_IO_URL,
domain,
}
}
2 changes: 2 additions & 0 deletions packages/synapse-react-client/src/mocks/msw/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { getResetTwoFactorAuthHandlers } from './handlers/resetTwoFactorAuthHand
import { getMessageHandlers } from './handlers/messageHandlers'
import { getFeatureFlagsOverride } from './handlers/featureFlagHandlers'
import { getDoiHandler } from './handlers/doiHandlers'
import { getShortIoHandlers } from './handlers/shortIoHandlers'

// Simple utility type that just indicates that the response body could be an error like the Synapse backend may send.
export type SynapseApiResponse<T> = T | SynapseError
Expand Down Expand Up @@ -75,6 +76,7 @@ const getHandlers = (backendOrigin: string, portalOrigin?: string) => [
getFeatureFlagsOverride({ portalOrigin }),
...getHandlersForTableQuery(backendOrigin),
...getDoiHandler(backendOrigin),
...getShortIoHandlers(),
]

const handlers = getHandlers(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { rest } from 'msw'
import { mockShortIoResponse } from '../../../mocks/mockShortIo'

export const getShortIoHandlers = () => [
rest.post('https://api.short.io/links/public', async (req, res, ctx) => {
const body = await req.json()
return res(
ctx.status(200),
ctx.json(mockShortIoResponse(body.originalURL, body.domain)),
)
}),
]

0 comments on commit 078eaf6

Please sign in to comment.