Skip to content

Commit

Permalink
feat(admin): add batch updating
Browse files Browse the repository at this point in the history
listens for changes to localized entities and offers batch translation for updated entities

fix #109
  • Loading branch information
layaxx committed Mar 24, 2023
1 parent d5039e8 commit bf88e10
Show file tree
Hide file tree
Showing 21 changed files with 633 additions and 10 deletions.
2 changes: 1 addition & 1 deletion playground/config/plugins.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = ({ env }) => ({
// ...
translate: {
resolve: "../plugin",
resolve: '../plugin',
enabled: true,
config: {
provider: 'deepl',
Expand Down
98 changes: 98 additions & 0 deletions plugin/admin/src/Hooks/useUpdateCollection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useState, useEffect } from 'react'
import { request } from '@strapi/helper-plugin'
import pluginId from '../pluginId'
import getTrad from '../utils/getTrad'
import useAlert from './useAlert'

export function useUpdateCollection() {
const [updates, setUpdates] = useState([])
const [refetchIndex, setRefetchIndex] = useState(true)

const { handleNotification } = useAlert()

const refetch = () => setRefetchIndex((prevRefetchIndex) => !prevRefetchIndex)

const fetchUpdates = async () => {
const { data, error } = await request(
`/${pluginId}/batch-update/updates/`,
{
method: 'GET',
}
)

if (error) {
handleNotification({
type: 'warning',
id: error.message,
defaultMessage: 'Failed to fetch Updates',
link: error.link,
})
} else if (Array.isArray(data)) {
setUpdates(data)
}
}

const dismissUpdates = async (ids) => {
for (const id of ids) {
const { error } = await request(
`/${pluginId}/batch-update/dismiss/${id}`,
{
method: 'DELETE',
}
)

if (error) {
handleNotification({
type: 'warning',
id: error.message,
defaultMessage: 'Failed to dismiss Update',
link: error.link,
})
}
}

refetch()
}

const startUpdate = async (ids, sourceLocale) => {
const { error } = await request(`/${pluginId}/batch-update`, {
method: 'POST',
body: {
updatedEntryIDs: ids,
sourceLocale,
},
})

if (error) {
handleNotification({
type: 'warning',
id: error.message,
defaultMessage: 'Failed to translate collection',
link: error.link,
})
} else {
refetch()
handleNotification({
type: 'success',
id: getTrad('batch-translate.start-success'),
defaultMessage: 'Request to translate content-type was successful',
blockTransition: false,
})
}
}

useEffect(() => {
fetchUpdates()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [refetchIndex])

return {
updates,
dismissUpdates,
startUpdate,
refetch,
handleNotification,
}
}

export default useUpdateCollection
230 changes: 230 additions & 0 deletions plugin/admin/src/components/BatchUpdateTable/Table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import React, { memo, useState, useEffect } from 'react'
import { Table, Tbody, Thead, Th, Tr, Td } from '@strapi/design-system/Table'
import { ExclamationMarkCircle } from '@strapi/icons'
import {
Box,
Flex,
Stack,
Button,
Checkbox,
Select,
Option,
Dialog,
DialogBody,
DialogFooter,
Typography,
} from '@strapi/design-system'
import { useIntl } from 'react-intl'
import useCollection from '../../Hooks/useCollection'
import useUpdateCollection from '../../Hooks/useUpdateCollection'
import { getTrad } from '../../utils'

const CollectionTable = () => {
const { formatMessage } = useIntl()

const [selectedIDs, setSelectedIDs] = useState([])
const [dialogOpen, setDialogOpen] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [sourceLocale, setSourceLocale] = useState(null)

const { locales } = useCollection()

useEffect(() => {
if (Array.isArray(locales) && locales.length > 0)
setSourceLocale(locales[0].code)
}, [locales])

const { updates, startUpdate, dismissUpdates, refetch } =
useUpdateCollection()

useEffect(() => {
const localeUses = updates
.filter(({ id }) => selectedIDs.includes(id))
.reduce((acc, current) => {
for (const locale of current.attributes.localesWithUpdates) {
acc[locale] = (acc[locale] ?? 0) + 1
}

return acc
}, {})

const { code: mostUpdatedLocale } = Object.entries(localeUses).reduce(
(acc, [code, uses]) => (acc.uses < uses ? { code, uses } : acc),
{ code: '', uses: 0 }
)

setSourceLocale(mostUpdatedLocale)
}, [selectedIDs, updates])

const onAction = () => {
setIsLoading(true)
startUpdate(selectedIDs, sourceLocale).then(() => {
setIsLoading(false)
setDialogOpen(false)
refetch()
})
}

const dismissSelected = () => {
dismissUpdates(selectedIDs).then(() => {
setSelectedIDs([])
refetch()
})
}

return (
<Box background="neutral100">
<Flex>
<Button
disabled={selectedIDs.length === 0}
onClick={() => setDialogOpen(true)}
>
Translate selected
</Button>
<Button
disabled={selectedIDs.length === 0}
onClick={() => dismissSelected()}
variant="danger"
>
Dismiss selected
</Button>
</Flex>
<Table>
<Thead>
<Tr>
<Th>
<Checkbox
value={selectedIDs.length > 0}
disabled={updates.length === 0}
indeterminate={
selectedIDs.length > 0 && selectedIDs.length < updates.length
}
onClick={() => {
if (selectedIDs.length === updates.length) {
setSelectedIDs([])
} else {
setSelectedIDs(updates.map(({ id }) => id))
}
}}
/>
</Th>
<Th>
<Typography variant="sigma">Type</Typography>
</Th>
<Th>
<Typography variant="sigma">IDs</Typography>
</Th>
{locales.map((locale) => (
<Th key={locale.code}>
<Typography variant="sigma">{locale.name}</Typography>
</Th>
))}
</Tr>
</Thead>
<Tbody>
{updates.map(({ id, attributes }, index) => (
<Tr key={id} aria-rowindex={index}>
<Td>
<Checkbox
value={selectedIDs.includes(id)}
onClick={() => {
if (selectedIDs.includes(id)) {
setSelectedIDs((selectedIDs) =>
selectedIDs.filter((param) => param !== id)
)
} else {
setSelectedIDs((selectedIDs) => [id, ...selectedIDs])
}
}}
/>
</Td>
<Td>
<Typography>{attributes.contentType}</Typography>
</Td>
<Td>
<Typography>
{attributes.groupID.split('-').join(',')}
</Typography>
</Td>
{locales.map(({ code }) => (
<Td key={code}>
<Typography>
{attributes.localesWithUpdates.includes(code)
? 'was updated'
: ''}
</Typography>
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
{dialogOpen && (
<Dialog
onClose={() => setDialogOpen(false)}
title={formatMessage({
id: getTrad(`batch-update.dialog.title`),
defaultMessage: 'Confirmation',
})}
isOpen={dialogOpen}
>
<DialogBody icon={<ExclamationMarkCircle />}>
<Stack spacing={2}>
<Flex justifyContent="center">
<Typography id="confirm-description">
{formatMessage({
id: getTrad(`batch-update.dialog.content`),
defaultMessage: 'Confirmation body',
})}
</Typography>
</Flex>
<Box>
<Stack spacing={2}>
<Select
label={formatMessage({
id: getTrad('batch-update.sourceLocale'),
})}
onChange={setSourceLocale}
value={sourceLocale}
>
{locales.map(({ name, code }) => {
return (
<Option key={code} value={code}>
{name}
</Option>
)
})}
</Select>
</Stack>
</Box>
</Stack>
</DialogBody>
<DialogFooter
startAction={
<Button onClick={() => setDialogOpen(false)} variant="tertiary">
{formatMessage({
id: 'popUpWarning.button.cancel',
defaultMessage: 'No, cancel',
})}
</Button>
}
endAction={
<Button
onClick={onAction}
disabled={!sourceLocale}
loading={isLoading}
>
{formatMessage({
id: getTrad(`batch-update.dialog.submit-text`),
defaultMessage: 'Confirm',
})}
</Button>
}
/>
</Dialog>
)}
</Box>
)
}

export default memo(CollectionTable)
1 change: 1 addition & 0 deletions plugin/admin/src/components/BatchUpdateTable/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as BatchUpdateTable } from './Table'
8 changes: 5 additions & 3 deletions plugin/admin/src/components/PluginPage/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React, { memo } from 'react'
import { Box } from '@strapi/design-system/Box'
import { Stack } from '@strapi/design-system/Stack'
import { CollectionTable } from '../Collection'
import { BatchUpdateTable } from '../BatchUpdateTable'
import UsageOverview from '../Usage'

const PluginPage = () => {
return (
<Box padding={8} margin={10} background="neutral">
<Stack padding={8} margin={10} spacing={4} background="neutral">
<CollectionTable />
<BatchUpdateTable />
<UsageOverview />
</Box>
</Stack>
)
}

Expand Down
2 changes: 1 addition & 1 deletion plugin/admin/src/components/Usage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const UsageOverview = () => {
)

return (
<Box background="neutral100" marginTop={4}>
<Box background="neutral100">
<Table colCount={3} rowCount={1}>
<Tbody>
<Tr>
Expand Down
6 changes: 5 additions & 1 deletion plugin/admin/src/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,9 @@
"usage.failed-to-load": "Daten konnten nicht geladen werden",
"usage.characters-used": "Zeichen verbraucht",
"usage.estimatedUsage": "Diese Aktion wird Ihre API-Nutzung ungefähr um diesen Betrag erhöht: ",
"usage.estimatedUsageExceedsQuota": "Diese Aktion wird voraussichtlich Ihr API-Kontingent überschreiten."
"usage.estimatedUsageExceedsQuota": "Diese Aktion wird voraussichtlich Ihr API-Kontingent überschreiten.",
"batch-update.dialog.title": "Massenanpassung starten",
"batch-update.dialog.content": "Für alle ausgewählten Elemente werden alle Lokalisierungen mit Übersetzungen der ausgewählten Ausgangssprache ersetzt.",
"batch-update.sourceLocale": "Ausgangssprache",
"batch-update.dialog.submit-text": "starten"
}
6 changes: 5 additions & 1 deletion plugin/admin/src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,9 @@
"usage.failed-to-load": "failed to load usage data",
"usage.characters-used": "characters used",
"usage.estimatedUsage": "This action is expected to increase your API usage by: ",
"usage.estimatedUsageExceedsQuota": "This action is expected to exceed your API Quota"
"usage.estimatedUsageExceedsQuota": "This action is expected to exceed your API Quota",
"batch-update.dialog.title": "start batch updating",
"batch-update.dialog.content": "All localizations of selected elements will be replaced by translations from the selected source locale.",
"batch-update.sourceLocale": "source locale",
"batch-update.dialog.submit-text": "start"
}
Loading

0 comments on commit bf88e10

Please sign in to comment.