omnivore/packages/web/pages/settings/rules.tsx
Thomas Rogers 89e78b377d fix(): Add network timeout to all yarn installs
Bump image-size from 1.2.0 to 1.2.1 (#4567)

Bumps [image-size](https://github.com/image-size/image-size) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/image-size/image-size/releases)
- [Commits](https://github.com/image-size/image-size/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: image-size
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

fix(): Add network timeout to all yarn installs

fix(): remove node-buffer

Bump brace-expansion from 1.1.11 to 1.1.12 in /pkg/admin (#4593)

Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump @babel/runtime-corejs3 from 7.26.0 to 7.28.2 (#4592)

Bumps [@babel/runtime-corejs3](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime-corejs3) from 7.26.0 to 7.28.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.2/packages/babel-runtime-corejs3)

---
updated-dependencies:
- dependency-name: "@babel/runtime-corejs3"
  dependency-version: 7.28.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump next from 13.5.8 to 14.2.30 (#4591)

Bumps [next](https://github.com/vercel/next.js) from 13.5.8 to 14.2.30.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v13.5.8...v14.2.30)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 14.2.30
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump axios from 0.27.2 to 0.30.0 (#4590)

Bumps [axios](https://github.com/axios/axios) from 0.27.2 to 0.30.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.30.0/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.27.2...v0.30.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 0.30.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump @babel/runtime from 7.14.6 to 7.28.2 in /pkg/admin (#4589)

Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.14.6 to 7.28.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.2/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.28.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump @radix-ui/react-separator from 0.1.4 to 1.1.6 (#4579)

Bumps [@radix-ui/react-separator](https://github.com/radix-ui/primitives) from 0.1.4 to 1.1.6.
- [Changelog](https://github.com/radix-ui/primitives/blob/main/release-process.md)
- [Commits](https://github.com/radix-ui/primitives/commits)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-separator"
  dependency-version: 1.1.6
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump http-proxy-middleware from 2.0.7 to 2.0.9 (#4574)

Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.7 to 2.0.9.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.7...v2.0.9)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-version: 2.0.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump @babel/helpers from 7.14.6 to 7.28.2 in /pkg/admin (#4594)

Bumps [@babel/helpers](https://github.com/babel/babel/tree/HEAD/packages/babel-helpers) from 7.14.6 to 7.28.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.2/packages/babel-helpers)

---
updated-dependencies:
- dependency-name: "@babel/helpers"
  dependency-version: 7.28.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump pg and @types/pg (#4547)

Bumps [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) and [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg). These dependencies needed to be updated together.

Updates `pg` from 8.13.1 to 8.13.3
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.13.3/packages/pg)

Updates `@types/pg` from 8.11.10 to 8.11.11
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pg)

---
updated-dependencies:
- dependency-name: pg
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: "@types/pg"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump @graphql-tools/utils from 9.2.1 to 10.8.6 (#4560)

Bumps [@graphql-tools/utils](https://github.com/ardatan/graphql-tools/tree/HEAD/packages/utils) from 9.2.1 to 10.8.6.
- [Release notes](https://github.com/ardatan/graphql-tools/releases)
- [Changelog](https://github.com/ardatan/graphql-tools/blob/master/packages/utils/CHANGELOG.md)
- [Commits](https://github.com/ardatan/graphql-tools/commits/@graphql-tools/utils@10.8.6/packages/utils)

---
updated-dependencies:
- dependency-name: "@graphql-tools/utils"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(): update versions

chore(): remove unused workload

fix(): kbar dependency

fix(): kbar dependency

chore(): fix packages

fix(): dependencies

fix(): dependencies

fix(): dependencies

fix(): dependencies api

fix(): dependencies api

fix(): dependencies api

chore(): fix order of github

fix(): fix tsconfig for liqe
2025-07-29 19:01:37 +02:00

473 lines
13 KiB
TypeScript

import { Button, Form, Input, Modal, Select, Space, Table, Tag } from 'antd'
import 'antd/dist/reset.css'
import { useCallback, useMemo, useState } from 'react'
import { Box, HStack } from '../../components/elements/LayoutPrimitives'
import { SettingsLayout } from '../../components/templates/SettingsLayout'
import { Label } from '../../lib/networking/fragments/labelFragment'
import { deleteRuleMutation } from '../../lib/networking/mutations/deleteRuleMutation'
import { setRuleMutation } from '../../lib/networking/mutations/setRuleMutation'
import { useGetIntegrationsQuery } from '../../lib/networking/queries/useGetIntegrationsQuery'
import {
Rule,
RuleAction,
RuleActionType,
RuleEventType,
useGetRulesQuery,
} from '../../lib/networking/queries/useGetRulesQuery'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
import { useGetLabels } from '../../lib/networking/labels/useLabels'
type CreateRuleModalProps = {
isModalOpen: boolean
setIsModalOpen: (isOpen: boolean) => void
revalidate: () => void
}
const eventTypeObj = {
PAGE_CREATED: 'PAGE_CREATED',
PAGE_UPDATED: 'PAGE_UPDATED',
HIGHLIGHT_CREATED: 'HIGHLIGHT_CREATED',
HIGHLIGHT_UPDATED: 'HIGHLIGHT_UPDATED',
LABEL_CREATED: 'LABEL_ATTACHED',
}
const CreateRuleModal = (props: CreateRuleModalProps): JSX.Element => {
const [form] = Form.useForm()
const onOk = async (values: any) => {
const name = form.getFieldValue('name')
const filter = form.getFieldValue('filter') || 'in:all' // default to all
const eventTypes = form.getFieldValue('eventTypes')
try {
await setRuleMutation({
name,
filter,
actions: [],
enabled: true,
eventTypes,
})
} catch (error) {
showErrorToast('Error creating rule')
return
}
form.resetFields()
props.setIsModalOpen(false)
props.revalidate()
showSuccessToast('Rule created')
}
const onCancel = (errorInfo: any) => {
form.resetFields()
props.setIsModalOpen(false)
}
return (
<Modal
title="Create Rule"
open={props.isModalOpen}
onOk={form.submit}
onCancel={onCancel}
destroyOnClose={true}
>
<Form
form={form}
name="createRule"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
onFinish={onOk}
// onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="Name"
name="name"
rules={[{ required: true, message: 'Please enter the rule name.' }]}
>
<Input />
</Form.Item>
<Form.Item label="Filter" name="filter">
<Input />
</Form.Item>
<Form.Item
label="When"
name="eventTypes"
rules={[
{
required: true,
message: 'Please select when the rule will be triggered',
},
]}
>
<Select
mode="multiple"
allowClear
placeholder="Please select when the rule will be triggered"
>
{Object.keys(RuleEventType).map((key: string, index: number) => {
const value = Object.values(RuleEventType)[index]
return (
<Select.Option key={key} value={value}>
{eventTypeObj[value]}
</Select.Option>
)
})}
</Select>
</Form.Item>
</Form>
</Modal>
)
}
type CreateActionModalProps = {
rule: Rule | undefined
setIsModalOpen: (isOpen: boolean) => void
revalidate: () => void
}
const CreateActionModal = (props: CreateActionModalProps): JSX.Element => {
const [form] = Form.useForm()
const { data: labels } = useGetLabels()
const { integrations } = useGetIntegrationsQuery()
const integrationOptions = ['NOTION', 'READWISE']
const isIntegrationEnabled = (integration: string): boolean => {
return integrations.some(
(i) => i.name.toUpperCase() === integration.toUpperCase()
)
}
const onOk = async (values: any) => {
const actionType = form.getFieldValue('actionType') as RuleActionType
let params = []
if (actionType == RuleActionType.AddLabel) {
params = form.getFieldValue('labels')
} else if (actionType == RuleActionType.Webhook) {
params = [form.getFieldValue('url')]
} else if (actionType == RuleActionType.Export) {
params = form.getFieldValue('integrations')
}
if (props.rule) {
// prevent adding duplicate actions
if (props.rule.actions.some((a) => a.type === actionType)) {
showErrorToast('Action already exists in the rule.')
return
}
await setRuleMutation({
id: props.rule.id,
name: props.rule.name,
filter: props.rule.filter,
enabled: props.rule.enabled,
actions: [
...props.rule.actions,
{
type: actionType,
params: params,
},
],
eventTypes: props.rule.eventTypes,
})
form.resetFields()
props.setIsModalOpen(false)
props.revalidate()
showSuccessToast('Action created')
}
}
const [actionType, setActionType] =
useState<RuleActionType | undefined>(undefined)
return (
<Modal
title="Create Action"
open={props.rule != undefined}
onOk={form.submit}
onCancel={() => {
form.resetFields()
props.setIsModalOpen(false)
}}
>
<Form
form={form}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
autoComplete="off"
onFinish={onOk}
>
<Form.Item
label="Action"
name="actionType"
rules={[{ required: true, message: 'Please choose an action' }]}
>
<Select
onSelect={(value) => {
console.log('setting action type', value)
setActionType(value)
}}
>
{Object.keys(RuleActionType).map((key: string, index: number) => {
const value = Object.values(RuleActionType)[index]
return (
<Select.Option key={key} value={value}>
{key}
</Select.Option>
)
})}
</Select>
</Form.Item>
{actionType == RuleActionType.AddLabel && (
<Form.Item
label="Labels"
name="labels"
rules={[
{ required: true, message: 'Please choose at least one label' },
]}
>
<Select mode="multiple">
{labels?.map((label) => {
return (
<Select.Option key={label.id} value={label.id}>
{label.name}
</Select.Option>
)
})}
</Select>
</Form.Item>
)}
{actionType == RuleActionType.Webhook && (
<Form.Item
label="URL"
name="url"
rules={[
{ required: true, message: 'Please key in your webhook url' },
]}
>
<Input />
</Form.Item>
)}
{actionType == RuleActionType.Export && (
<Form.Item
label="Integrations"
name="integrations"
hasFeedback
rules={[
{
required: true,
message: 'Please choose at least one integration',
},
{
validator: (_, value: string[]) => {
value.forEach((v) => {
if (!isIntegrationEnabled(v)) {
return Promise.reject(`Integration ${v} is not enabled`)
}
})
return Promise.resolve()
},
},
]}
>
<Select mode="multiple">
{integrationOptions.map((integration) => {
return (
<Select.Option key={integration} value={integration}>
{isIntegrationEnabled(integration) ? (
integration
) : (
<Button type="link" href="/settings/integrations">
Connect to {integration}
</Button>
)}
</Select.Option>
)
})}
</Select>
</Form.Item>
)}
</Form>
</Modal>
)
}
export default function Rules(): JSX.Element {
const { rules, revalidate } = useGetRulesQuery()
const { data: labels } = useGetLabels()
const [isCreateRuleModalOpen, setIsCreateRuleModalOpen] = useState(false)
const [createActionRule, setCreateActionRule] =
useState<Rule | undefined>(undefined)
const dataSource = useMemo(() => {
return rules.map((rule: Rule) => {
return {
rule: rule,
name: rule.name,
filter: rule.filter,
actions: rule.actions,
eventTypes: rule.eventTypes,
failedAt: rule.failedAt,
}
})
}, [rules])
applyStoredTheme()
const deleteRule = useCallback(
async (rule: Rule) => {
if (!(await deleteRuleMutation(rule.id))) {
showErrorToast('Error deleting rule')
} else {
showSuccessToast('Rule deleted')
}
revalidate()
},
[rules, revalidate]
)
const stringForActionParam = useCallback(
(actionType: RuleActionType, param: string): string => {
if (actionType === RuleActionType.AddLabel) {
if (!labels) {
return ''
}
return (
labels.find((label: Label) => {
const result = label.id == param
return result
})?.name ?? 'unknown'
)
}
return param
},
[labels]
)
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Filter',
dataIndex: 'filter',
key: 'filter',
},
{
title: 'When',
render: (text: string, row: { eventTypes: RuleEventType[] }) => (
<>
{row.eventTypes.map((eventType: RuleEventType, index: number) => {
return (
<Tag color={'geekblue'} key={index}>
{eventTypeObj[eventType]}
</Tag>
)
})}
</>
),
},
{
title: 'Actions',
render: (text: string, row: { actions: RuleAction[] }) => (
<>
{row.actions.map((action: RuleAction, index: number) => {
const color = action.type.length > 5 ? 'geekblue' : 'green'
return (
<Tag color={color} key={index} style={{ whiteSpace: 'unset' }}>
{action.type}(
{action.params.map((param: string, index: number) => {
const paramString = stringForActionParam(action.type, param)
return `"${paramString}${
index == action.params.length - 1 ? '"' : '", '
}`
})}
)
</Tag>
)
})}
</>
),
},
{
title: 'Failed At',
dataIndex: 'failedAt',
key: 'failedAt',
},
{
title: '',
key: 'tools',
render: (_: any, record: { rule: Rule }) => {
return (
<Space size="middle">
<Button
type="primary"
onClick={() => {
setCreateActionRule(record.rule)
}}
>
Add Action
</Button>
<Button
type="default"
onClick={async () => {
await deleteRule(record.rule)
}}
>
Delete
</Button>
</Space>
)
},
},
]
return (
<SettingsLayout>
<CreateRuleModal
revalidate={revalidate}
isModalOpen={isCreateRuleModalOpen}
setIsModalOpen={setIsCreateRuleModalOpen}
/>
<CreateActionModal
rule={createActionRule}
setIsModalOpen={(isOpen) => {
if (!isOpen) {
setCreateActionRule(undefined)
}
}}
revalidate={revalidate}
/>
<Box
css={{ pt: '44px', px: '10%', '@smDown': { px: '0' }, width: '100%' }}
>
<HStack css={{ py: '16px' }} distribution="end">
<Button
onClick={() => {
setIsCreateRuleModalOpen(true)
}}
type="primary"
>
Create a new Rule
</Button>
</HStack>
<Table
dataSource={dataSource}
columns={columns}
// expandable={{ expandedRowRender, indentSize: 30 }}
/>
</Box>
</SettingsLayout>
)
}