Skip to content

Instantly share code, notes, and snippets.

@runeb
Created January 31, 2023 01:11
Show Gist options
  • Save runeb/f9d59702c2580249b3df638decdb0e19 to your computer and use it in GitHub Desktop.
Save runeb/f9d59702c2580249b3df638decdb0e19 to your computer and use it in GitHub Desktop.
Example of a custom tool to discover possibly orphaned documents, create a mutation to delete them and optionally perform the mutation
import * as React from 'react'
import {definePlugin, SanityClient, useClient, useSchema} from 'sanity'
import type {Tool} from 'sanity'
import {Card, Stack, Grid, Checkbox, Flex, Box, Text, Heading, Button} from '@sanity/ui'
import {useEffect} from 'react'
type ToolProps = {
tool: Tool
}
const Mutation = ({typeNames, client}: {typeNames: string[]; client: SanityClient}) => {
const mutation = {
delete: {
query: '*[_type in $types]',
params: {
types: typeNames,
},
},
}
const onClick = () => {
client
.mutate([mutation])
.then(console.log)
.catch(console.error)
.finally(() => alert('See console for results, reload to update schema types'))
}
return (
<Card>
<pre>{JSON.stringify(mutation, null, 2)}</pre>
<Button onClick={onClick} disabled={!typeNames || typeNames.length === 0} tone="critical">
Delete
</Button>
</Card>
)
}
const ToolComponent = ({tool}: ToolProps) => {
const schema = useSchema()
const client = useClient({apiVersion: '2021-03-25'})
const [undefinedNames, setUndefinedNames] = React.useState<string[]>()
const [schemaNames, setSchemaNames] = React.useState<string[]>()
const [selected, setSelected] = React.useState<Record<string, any>>({})
const [count, setCount] = React.useState<number>()
useEffect(() => {
const documentNames = schema._original?.types
.filter((type) => type.type === 'document')
.map((type) => type.name)
setSchemaNames(documentNames)
}, [schema])
useEffect(() => {
console.log('Fetching undefined names')
client.fetch<string[]>('array::unique(*[]._type)').then((names) => {
// Pick out the names that do not appear in documentNames
const types = names.filter((name) => !schemaNames?.includes(name))
setUndefinedNames(types)
setSelected({})
})
}, [client, schemaNames])
useEffect(() => {
if (selected) {
const types = Object.keys(selected).filter((key) => selected[key])
client.fetch(`count(*[_type in $types])`, {types}).then(setCount)
}
}, [selected, client])
return (
<Card padding={2}>
<Stack space={2}>
<Heading>Document types in dataset not in schema definition</Heading>
<Text>
Note that in certain cases there may be a different workspace or Studio that cares about
those schema types!
</Text>
<Grid columns={[2, 3, 4, 6]} gap={[1, 1, 2, 3]} padding={4}>
{undefinedNames?.map((name) => (
<React.Fragment key={name}>
<Flex align="center">
<Checkbox
id="checkbox"
style={{display: 'block'}}
onChange={() => {
setSelected({...selected, [name]: !selected[name]})
}}
/>
<Box flex={1} paddingLeft={3}>
<Text>
<label htmlFor="checkbox">{name}</label>
</Text>
</Box>
</Flex>
</React.Fragment>
))}
</Grid>
</Stack>
<Stack space={2}>
<Heading>Mutation for deleting documents</Heading>
<Text>Matches {count} documents</Text>
</Stack>
<Mutation client={client} typeNames={Object.keys(selected).filter((key) => selected[key])} />
</Card>
)
}
const tools: Tool[] = [
{
name: 'delete-undefined',
title: 'Unknown docs',
component: ToolComponent,
},
]
export const deleteUndefined = definePlugin({
name: 'delete-undefined',
tools,
})
import {defineConfig} from 'sanity'
import {deskTool} from 'sanity/desk'
import {visionTool} from '@sanity/vision'
import {schemaTypes} from './schemas'
// Importing the plugin defined in the other file
import { deleteUndefined } from './deleteUndefined'
export default defineConfig({
name: 'default',
title: 'Clean Sandbox',
projectId: 'kbrhtt13',
dataset: 'v3',
plugins: [
deskTool(),
visionTool(),
// Here we add it to the Studio
deleteUndefined()
],
schema: {
types: schemaTypes,
},
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment