sanity-plugin-dashboard-widget-netlify 2.0.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -46
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +220 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -100
- package/lib/index.d.mts +0 -45
- package/lib/index.d.ts +0 -45
- package/lib/index.js +0 -172
- package/lib/index.js.map +0 -1
- package/lib/index.mjs +0 -176
- package/lib/index.mjs.map +0 -1
- package/sanity.json +0 -8
- package/src/components/NetlifyWidget.tsx +0 -47
- package/src/components/SiteItem/Links.tsx +0 -40
- package/src/components/SiteItem/index.tsx +0 -79
- package/src/components/SiteList.tsx +0 -42
- package/src/datastores/deploy.ts +0 -14
- package/src/http/jsonRequest.ts +0 -33
- package/src/http/statusCodeRequest.ts +0 -33
- package/src/http/utils/createAbortController.ts +0 -8
- package/src/index.ts +0 -7
- package/src/plugin.tsx +0 -16
- package/src/props.ts +0 -55
- package/src/reducers.ts +0 -53
- package/src/types.ts +0 -33
- package/src/widget.tsx +0 -18
- package/v2-incompatible.js +0 -11
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import React, {FunctionComponent, useCallback, useEffect, useRef, useState} from 'react'
|
|
2
|
-
import {Button, Flex, Box, Card, Text, Stack, Label} from '@sanity/ui'
|
|
3
|
-
import {DeployAction, Site} from '../../types'
|
|
4
|
-
import Links from './Links'
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
site: Site
|
|
8
|
-
onDeploy: DeployAction
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const IMAGE_PULL_INTERVAL = 10000
|
|
12
|
-
|
|
13
|
-
const getImageUrl = (siteId: string, branchName?: string) => {
|
|
14
|
-
const baseUrl = `https://api.netlify.com/api/v1/badges/${siteId}/deploy-status`
|
|
15
|
-
const time = new Date().getTime()
|
|
16
|
-
const branch = `branch=${branchName}`
|
|
17
|
-
|
|
18
|
-
return branchName ? `${baseUrl}?${time}&${branch}` : `${baseUrl}?${time}`
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const useBadgeImage = (siteId: string, branchName?: string ) => {
|
|
22
|
-
const [src, setSrc] = useState(() => getImageUrl(siteId, branchName))
|
|
23
|
-
const update = useCallback(() => setSrc(getImageUrl(siteId, branchName)), [siteId])
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
const interval = window.setInterval(update, IMAGE_PULL_INTERVAL)
|
|
27
|
-
return () => window.clearInterval(interval)
|
|
28
|
-
}, [update])
|
|
29
|
-
|
|
30
|
-
return [src, update] as const
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const useDeploy = (site: Site, onDeploy: DeployAction, updateBadge: () => void) => {
|
|
34
|
-
const timeoutRef = useRef(-1)
|
|
35
|
-
useEffect(() => () => window.clearTimeout(timeoutRef.current), [])
|
|
36
|
-
|
|
37
|
-
return useCallback(() => {
|
|
38
|
-
onDeploy(site)
|
|
39
|
-
timeoutRef.current = window.setTimeout(updateBadge, 1000)
|
|
40
|
-
}, [site, onDeploy, updateBadge])
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const SiteItem: FunctionComponent<Props> = (props) => {
|
|
44
|
-
const [hasBadgeError, setHasBadgeError] = useState(false)
|
|
45
|
-
const {site, onDeploy} = props
|
|
46
|
-
const {id, name, title, url, adminUrl, buildHookId, branch} = site
|
|
47
|
-
|
|
48
|
-
const [badge, updateBadge] = useBadgeImage(id, branch)
|
|
49
|
-
const handleDeploy = useDeploy(site, onDeploy, updateBadge)
|
|
50
|
-
const handleBadgeError = () => {
|
|
51
|
-
setHasBadgeError(true)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<Flex as="li">
|
|
56
|
-
<Box flex={1} paddingY={2} paddingX={3}>
|
|
57
|
-
<Stack space={2}>
|
|
58
|
-
<Text as="h4">
|
|
59
|
-
{title || name}
|
|
60
|
-
<Links url={url} adminUrl={adminUrl} />
|
|
61
|
-
</Text>
|
|
62
|
-
|
|
63
|
-
<Flex justify="flex-start">
|
|
64
|
-
{!hasBadgeError && <img src={badge} onError={handleBadgeError} alt="Badge" />}
|
|
65
|
-
{hasBadgeError && <Card tone="critical" radius={2} padding={2}><Label size={0} muted>Failed to load badge</Label></Card>}
|
|
66
|
-
</Flex>
|
|
67
|
-
</Stack>
|
|
68
|
-
</Box>
|
|
69
|
-
|
|
70
|
-
{buildHookId ? (
|
|
71
|
-
<Box paddingY={2} paddingX={3}>
|
|
72
|
-
<Button mode="ghost" onClick={handleDeploy} text="Deploy" />
|
|
73
|
-
</Box>
|
|
74
|
-
) : null}
|
|
75
|
-
</Flex>
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export default SiteItem
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import {DeployAction, Site} from '../types'
|
|
3
|
-
import SiteItem from './SiteItem'
|
|
4
|
-
import {Flex, Box, Card, Text, Spinner, Stack} from '@sanity/ui'
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
isLoading: boolean
|
|
8
|
-
sites?: Site[]
|
|
9
|
-
onDeploy: DeployAction
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default function SiteList(props: Props) {
|
|
13
|
-
const {isLoading, onDeploy, sites} = props
|
|
14
|
-
if (isLoading) {
|
|
15
|
-
return (
|
|
16
|
-
<Card padding={4}>
|
|
17
|
-
<Flex direction="column" justify="center" align="center">
|
|
18
|
-
<Spinner muted />
|
|
19
|
-
<Box marginTop={3}>
|
|
20
|
-
<Text muted>Loading sites…</Text>
|
|
21
|
-
</Box>
|
|
22
|
-
</Flex>
|
|
23
|
-
</Card>
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
if (!sites || (sites && sites.length === 0)) {
|
|
27
|
-
return (
|
|
28
|
-
<Card tone="critical" padding={3}>
|
|
29
|
-
<Text>No sites are defined in the widget options. Please check your config.</Text>
|
|
30
|
-
</Card>
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
return (
|
|
34
|
-
<Box paddingY={2}>
|
|
35
|
-
<Stack as="ul" space={2}>
|
|
36
|
-
{sites.map((site, index) => {
|
|
37
|
-
return <SiteItem onDeploy={onDeploy} site={site} key={`site-${index}`} />
|
|
38
|
-
})}
|
|
39
|
-
</Stack>
|
|
40
|
-
</Box>
|
|
41
|
-
)
|
|
42
|
-
}
|
package/src/datastores/deploy.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import {Observable, of} from 'rxjs'
|
|
2
|
-
import {map} from 'rxjs/operators'
|
|
3
|
-
import {statusCodeRequest} from '../http/statusCodeRequest'
|
|
4
|
-
import {Site} from '../types'
|
|
5
|
-
|
|
6
|
-
export function deploy(site: Site): Observable<{result: number; site: Site} | Error> {
|
|
7
|
-
if (!site.buildHookId) {
|
|
8
|
-
return of(new Error('Site missing buildHookId'))
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return statusCodeRequest(`https://api.netlify.com/build_hooks/${site.buildHookId}`, {
|
|
12
|
-
method: 'POST',
|
|
13
|
-
}).pipe(map((result) => ({result, site})))
|
|
14
|
-
}
|
package/src/http/jsonRequest.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import {Observable} from 'rxjs'
|
|
2
|
-
import {createAbortController} from './utils/createAbortController'
|
|
3
|
-
|
|
4
|
-
// eslint-disable-next-line no-undef
|
|
5
|
-
export const jsonRequest = <T>(input: RequestInfo, init?: RequestInit): Observable<T> => {
|
|
6
|
-
return new Observable((subscriber) => {
|
|
7
|
-
const controller = createAbortController()
|
|
8
|
-
const onResponse = (res: T) => {
|
|
9
|
-
subscriber.next(res)
|
|
10
|
-
subscriber.complete()
|
|
11
|
-
}
|
|
12
|
-
const onError = (err: Error) => {
|
|
13
|
-
if (err.name === 'AbortError') {
|
|
14
|
-
subscriber.complete()
|
|
15
|
-
} else {
|
|
16
|
-
subscriber.error(err)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
fetch(input, {...init, signal: controller.signal})
|
|
21
|
-
.then((res: Response) => {
|
|
22
|
-
if (res.status < 200 || res.status > 299) {
|
|
23
|
-
throw new Error(`HTTP Error ${res.status}: ${res.statusText}`)
|
|
24
|
-
}
|
|
25
|
-
return res.json()
|
|
26
|
-
})
|
|
27
|
-
.then(onResponse, onError)
|
|
28
|
-
|
|
29
|
-
return () => {
|
|
30
|
-
controller.abort()
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import {Observable} from 'rxjs'
|
|
2
|
-
import {createAbortController} from './utils/createAbortController'
|
|
3
|
-
|
|
4
|
-
// eslint-disable-next-line no-undef
|
|
5
|
-
export const statusCodeRequest = (input: RequestInfo, init?: RequestInit): Observable<number> => {
|
|
6
|
-
return new Observable((subscriber) => {
|
|
7
|
-
const controller = createAbortController()
|
|
8
|
-
const onResponse = (res: number) => {
|
|
9
|
-
subscriber.next(res)
|
|
10
|
-
subscriber.complete()
|
|
11
|
-
}
|
|
12
|
-
const onError = (err: Error) => {
|
|
13
|
-
if (err.name === 'AbortError') {
|
|
14
|
-
subscriber.complete()
|
|
15
|
-
} else {
|
|
16
|
-
subscriber.error(err)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
fetch(input, {...init, signal: controller.signal})
|
|
21
|
-
.then((res: Response) => {
|
|
22
|
-
if (res.status < 200 || res.status > 299) {
|
|
23
|
-
throw new Error(`HTTP Error ${res.status}: ${res.statusText}`)
|
|
24
|
-
}
|
|
25
|
-
return res.status
|
|
26
|
-
})
|
|
27
|
-
.then(onResponse, onError)
|
|
28
|
-
|
|
29
|
-
return () => {
|
|
30
|
-
controller.abort()
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
}
|
package/src/index.ts
DELETED
package/src/plugin.tsx
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import Widget from './widget'
|
|
3
|
-
import {WidgetOptions} from './types'
|
|
4
|
-
import {DashboardWidget, LayoutConfig} from '@sanity/dashboard'
|
|
5
|
-
|
|
6
|
-
export type NetlifyWidgetConfig = WidgetOptions & {layout?: LayoutConfig}
|
|
7
|
-
|
|
8
|
-
export function netlifyWidget(config: NetlifyWidgetConfig): DashboardWidget {
|
|
9
|
-
return {
|
|
10
|
-
name: 'netlify-widget',
|
|
11
|
-
component: () => {
|
|
12
|
-
return <Widget {...config} />
|
|
13
|
-
},
|
|
14
|
-
layout: config.layout ?? {width: 'medium'},
|
|
15
|
-
}
|
|
16
|
-
}
|
package/src/props.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import {merge, of} from 'rxjs'
|
|
2
|
-
import {createEventHandler} from 'react-props-stream'
|
|
3
|
-
import {catchError, map, startWith, switchMap} from 'rxjs/operators'
|
|
4
|
-
import {deploy} from './datastores/deploy'
|
|
5
|
-
import {Site, WidgetOptions} from './types'
|
|
6
|
-
import {stateReducer$} from './reducers'
|
|
7
|
-
|
|
8
|
-
const noop = () => undefined
|
|
9
|
-
|
|
10
|
-
const INITIAL_PROPS = {
|
|
11
|
-
title: 'Netlify sites',
|
|
12
|
-
sites: [],
|
|
13
|
-
isLoading: true,
|
|
14
|
-
onDeploy: noop,
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
18
|
-
export const props$ = (options: WidgetOptions) => {
|
|
19
|
-
const configuredSites = (options.sites || []).map((site) => ({
|
|
20
|
-
id: site.apiId,
|
|
21
|
-
name: site.name,
|
|
22
|
-
title: site.title,
|
|
23
|
-
buildHookId: site.buildHookId,
|
|
24
|
-
url:
|
|
25
|
-
site.url ||
|
|
26
|
-
(site.branch && `https://${site.branch}--${site.name}.netlify.app/`) ||
|
|
27
|
-
(site.name && `https://${site.name}.netlify.app/`),
|
|
28
|
-
adminUrl: site.name && `https://app.netlify.com/sites/${site.name}`,
|
|
29
|
-
branch: site.branch,
|
|
30
|
-
}))
|
|
31
|
-
|
|
32
|
-
const [onDeploy$, onDeploy] = createEventHandler<Site>()
|
|
33
|
-
const setSitesAction$ = of(configuredSites).pipe(map((sites) => ({type: 'setSites', sites})))
|
|
34
|
-
const deployAction$ = onDeploy$.pipe(map((site) => ({type: 'deploy/started', site})))
|
|
35
|
-
const deployResult$ = onDeploy$.pipe(switchMap((site) => deploy(site)))
|
|
36
|
-
const deployCompletedAction$ = deployResult$.pipe(
|
|
37
|
-
map(
|
|
38
|
-
(result) => ({type: 'deploy/completed', ...result}),
|
|
39
|
-
catchError((error) => of({type: 'deploy/failed', error}))
|
|
40
|
-
)
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
merge(setSitesAction$, deployAction$, deployCompletedAction$).pipe(stateReducer$).subscribe()
|
|
44
|
-
|
|
45
|
-
return of(configuredSites).pipe(
|
|
46
|
-
map((sites) => ({
|
|
47
|
-
sites,
|
|
48
|
-
title: options.title || INITIAL_PROPS.title,
|
|
49
|
-
description: options.description,
|
|
50
|
-
isLoading: false,
|
|
51
|
-
onDeploy,
|
|
52
|
-
})),
|
|
53
|
-
startWith(INITIAL_PROPS)
|
|
54
|
-
)
|
|
55
|
-
}
|
package/src/reducers.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import {scan} from 'rxjs/operators'
|
|
2
|
-
import {Site} from './types'
|
|
3
|
-
|
|
4
|
-
interface Deployment {
|
|
5
|
-
id: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
interface Action {
|
|
9
|
-
type: string
|
|
10
|
-
sites?: Site[]
|
|
11
|
-
site?: Site
|
|
12
|
-
error?: Error
|
|
13
|
-
deployments?: Deployment[]
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface State {
|
|
17
|
-
sites: Site[]
|
|
18
|
-
action: Action
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const stateReducer$ = scan((state: State, action: Action) => {
|
|
22
|
-
switch (action.type) {
|
|
23
|
-
case 'setSites':
|
|
24
|
-
return {...state, sites: action.sites || []}
|
|
25
|
-
case 'deploy/started':
|
|
26
|
-
return {
|
|
27
|
-
...state,
|
|
28
|
-
sites: state.sites.map((site: Site) => {
|
|
29
|
-
if (action.site && site.id === action.site.id) {
|
|
30
|
-
return {...site}
|
|
31
|
-
}
|
|
32
|
-
return site
|
|
33
|
-
}),
|
|
34
|
-
}
|
|
35
|
-
case 'deploy/failed':
|
|
36
|
-
return {
|
|
37
|
-
...state,
|
|
38
|
-
error: action.error,
|
|
39
|
-
}
|
|
40
|
-
case 'deploy/completed':
|
|
41
|
-
return {
|
|
42
|
-
...state,
|
|
43
|
-
sites: state.sites.map((site: Site) => {
|
|
44
|
-
if (action.site && site.id === action.site.id) {
|
|
45
|
-
return {...site, error: action.error}
|
|
46
|
-
}
|
|
47
|
-
return site
|
|
48
|
-
}),
|
|
49
|
-
}
|
|
50
|
-
default:
|
|
51
|
-
return state
|
|
52
|
-
}
|
|
53
|
-
})
|
package/src/types.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
export interface SiteWidgetOption {
|
|
2
|
-
apiId: string
|
|
3
|
-
name?: string
|
|
4
|
-
title: string
|
|
5
|
-
buildHookId: string
|
|
6
|
-
url?: string
|
|
7
|
-
branch?: string
|
|
8
|
-
}
|
|
9
|
-
export interface WidgetOptions {
|
|
10
|
-
title?: string
|
|
11
|
-
description?: string
|
|
12
|
-
sites: SiteWidgetOption[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface Site {
|
|
16
|
-
title: string
|
|
17
|
-
name?: string
|
|
18
|
-
id: string
|
|
19
|
-
url?: string
|
|
20
|
-
adminUrl?: string
|
|
21
|
-
buildHookId: string
|
|
22
|
-
branch?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type DeployAction = (site: Site) => void
|
|
26
|
-
|
|
27
|
-
export interface NetlifyWidgetProps {
|
|
28
|
-
title?: string
|
|
29
|
-
description?: string
|
|
30
|
-
sites?: Site[]
|
|
31
|
-
isLoading: boolean
|
|
32
|
-
onDeploy: DeployAction
|
|
33
|
-
}
|
package/src/widget.tsx
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import {streamingComponent} from 'react-props-stream'
|
|
3
|
-
import {map, switchMap} from 'rxjs/operators'
|
|
4
|
-
import {WidgetOptions} from './types'
|
|
5
|
-
import {props$} from './props'
|
|
6
|
-
import NetlifyWidget from './components/NetlifyWidget'
|
|
7
|
-
|
|
8
|
-
export default streamingComponent<WidgetOptions>((options$) =>
|
|
9
|
-
options$.pipe(
|
|
10
|
-
switchMap((options) =>
|
|
11
|
-
props$(options).pipe(
|
|
12
|
-
map((props) => {
|
|
13
|
-
return <NetlifyWidget {...props} />
|
|
14
|
-
})
|
|
15
|
-
)
|
|
16
|
-
)
|
|
17
|
-
)
|
|
18
|
-
)
|
package/v2-incompatible.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const {showIncompatiblePluginDialog} = require('@sanity/incompatible-plugin')
|
|
2
|
-
const {name, version, sanityExchangeUrl} = require('./package.json')
|
|
3
|
-
|
|
4
|
-
export default showIncompatiblePluginDialog({
|
|
5
|
-
name: name,
|
|
6
|
-
versions: {
|
|
7
|
-
v3: version,
|
|
8
|
-
v2: '^1.3.1',
|
|
9
|
-
},
|
|
10
|
-
sanityExchangeUrl,
|
|
11
|
-
})
|