sanity-plugin-dashboard-widget-netlify 2.0.3 → 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.
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -1,8 +0,0 @@
1
- import AbortControllerPolyfill from 'abort-controller'
2
-
3
- export const createAbortController = (): AbortController => {
4
- if (!('AbortController' in window)) {
5
- return new AbortControllerPolyfill() as AbortController
6
- }
7
- return new AbortController()
8
- }
package/src/index.ts DELETED
@@ -1,7 +0,0 @@
1
- /**
2
- * Plugin widget for @sanity/dashboard
3
- */
4
- export {netlifyWidget} from './plugin'
5
- export type {NetlifyWidgetConfig} from './plugin'
6
-
7
- export * from './types'
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
- )
@@ -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
- })