Skip to content

Commit

Permalink
feat: manual trigger for specific functions (#1238)
Browse files Browse the repository at this point in the history
* feat: manual trigger for specific functions

* refactors: remove export, change cron format
  • Loading branch information
1emu committed Sep 5, 2023
1 parent d6da9ea commit 7b80fe9
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 13 deletions.
4 changes: 2 additions & 2 deletions src/back/jobs/BadgeAirdrop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export async function runAirdropJobs() {
await Promise.all([runQueuedAirdropJobs(), giveAndRevokeLandOwnerBadges()])
}

async function runQueuedAirdropJobs() {
export async function runQueuedAirdropJobs() {
const pendingJobs = await AirdropJobModel.getPending()
if (pendingJobs.length === 0) {
return
Expand All @@ -31,7 +31,7 @@ async function runQueuedAirdropJobs() {
})
}

async function giveAndRevokeLandOwnerBadges() {
export async function giveAndRevokeLandOwnerBadges() {
if (isProdEnv()) {
await BadgesService.giveAndRevokeLandOwnerBadges()
}
Expand Down
27 changes: 27 additions & 0 deletions src/back/routes/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import routes from 'decentraland-gatsby/dist/entities/Route/routes'

import { DEBUG_ADDRESSES } from '../../entities/Debug/isDebugAddress'
import { ErrorService } from '../../services/ErrorService'
import { giveAndRevokeLandOwnerBadges, giveTopVoterBadges, runQueuedAirdropJobs } from '../jobs/BadgeAirdrop'
import { validateDebugAddress } from '../utils/validations'

const FUNCTIONS_MAP: { [key: string]: () => Promise<any> } = {
runQueuedAirdropJobs,
giveAndRevokeLandOwnerBadges,
giveTopVoterBadges,
}

export default routes((router) => {
const withAuth = auth()
Expand All @@ -12,8 +20,27 @@ export default routes((router) => {
handleAPI(async () => DEBUG_ADDRESSES)
)
router.post('/debug/report-error', withAuth, handleAPI(reportClientError))
router.post('/debug/trigger', withAuth, handleAPI(triggerFunction))
})

function reportClientError(req: WithAuth): void {
ErrorService.report(req.body.message, { frontend: true, ...req.body.extraInfo })
}

async function triggerFunction(req: WithAuth) {
const user = req.auth!
validateDebugAddress(user)

const { functionName } = req.body

if (FUNCTIONS_MAP[functionName]) {
try {
const result = await FUNCTIONS_MAP[functionName]()
return { message: `Function '${functionName}' executed successfully.`, result }
} catch (error) {
throw new Error(`Error executing '${functionName}' function: ${error}`)
}
} else {
throw new Error(`Function '${functionName}' not found.`)
}
}
8 changes: 8 additions & 0 deletions src/clients/Governance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,14 @@ export class Governance extends API {
return response.data
}

async triggerFunction(functionName: string) {
const response = await this.fetch<ApiResponse<string>>(
`/debug/trigger`,
this.options().method('POST').authorization({ sign: true }).json({ functionName })
)
return response.data
}

async checkUrlTitle(url: string) {
const response = await this.fetch<ApiResponse<{ title?: string }>>(
`/url-title`,
Expand Down
9 changes: 2 additions & 7 deletions src/components/Debug/BadgesAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,14 @@ export default function BadgesAdmin({ className }: Props) {
<Heading size="xs">{'Create, Airdrop, Revoke'}</Heading>
<div>
<Button
className="Debug__SideButton"
className="Debug__SectionButton"
primary
disabled={formDisabled}
onClick={() => handleCreateBadgeSpec()}
>
{'Create Badge Spec'}
</Button>
<Button
className="Debug__SectionButton"
primary
disabled={formDisabled}
onClick={() => handleAirdropBadge()}
>
<Button className="Debug__SideButton" primary disabled={formDisabled} onClick={() => handleAirdropBadge()}>
{'Airdrop'}
</Button>
<Button className="Debug__SideButton" primary disabled={formDisabled} onClick={() => handleRevokeBadge()}>
Expand Down
98 changes: 98 additions & 0 deletions src/components/Debug/TriggerFunction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, { useState } from 'react'

import { Button } from 'decentraland-ui/dist/components/Button/Button'
import { SelectField } from 'decentraland-ui/dist/components/SelectField/SelectField'

import { Governance } from '../../clients/Governance'
import Heading from '../Common/Typography/Heading'
import Label from '../Common/Typography/Label'
import Text from '../Common/Typography/Text'
import ErrorMessage from '../Error/ErrorMessage'
import { ContentSection } from '../Layout/ContentLayout'

interface Props {
className?: string
}

const FUNCTION_NAME_OPTIONS = [
{
text: 'runQueuedAirdropJobs',
value: 'runQueuedAirdropJobs',
},
{
text: 'giveAndRevokeLandOwnerBadges',
value: 'giveAndRevokeLandOwnerBadges',
},
{
text: 'giveTopVoterBadges',
value: 'giveTopVoterBadges',
},
]

export default function TriggerFunction({ className }: Props) {
const [functionName, setFunctionName] = useState<string>('')
const [result, setResult] = useState<string>('')
const [errorMessage, setErrorMessage] = useState<string | undefined | null>()
const [formDisabled, setFormDisabled] = useState(false)

async function handleTriggerFunction() {
if (functionName && functionName.length > 0) {
await submit(
async () => Governance.get().triggerFunction(functionName),
(result) => setResult(JSON.stringify(result))
)
}
}

async function submit<T>(submit: () => Promise<T>, update: (result: T) => void) {
setFormDisabled(true)
Promise.resolve()
.then(async () => {
const result: T = await submit()
update(result)
})
.then(() => {
setFormDisabled(false)
setErrorMessage('')
})
.catch((err) => {
console.error(err, { ...err })
setErrorMessage(err.message)
setFormDisabled(false)
})
}

return (
<div className={className}>
<ContentSection>
<Heading size="sm">{'Trigger Function'}</Heading>
<div>
<div>
<Button
className="Debug__SectionButton"
primary
disabled={formDisabled}
onClick={() => handleTriggerFunction()}
>
{'Pimbi'}
</Button>
</div>
<Label>{'Function Name'}</Label>
<SelectField
value={functionName}
onChange={(_, { value }) => setFunctionName(value as string)}
options={FUNCTION_NAME_OPTIONS}
disabled={formDisabled}
/>
{result && (
<>
<Label>{'Result'}</Label>
<Text className="Debug__Result">{result}</Text>
</>
)}
</div>
</ContentSection>
{!!errorMessage && <ErrorMessage label={'Trigger Error'} errorMessage={errorMessage} />}
</div>
)
}
2 changes: 2 additions & 0 deletions src/pages/debug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import EnvStatus from '../components/Debug/EnvStatus'
import ErrorReporting from '../components/Debug/ErrorReporting'
import HttpStatus from '../components/Debug/HttpStatus'
import SnapshotStatus from '../components/Debug/SnapshotStatus'
import TriggerFunction from '../components/Debug/TriggerFunction'
import Navigation, { NavigationTab } from '../components/Layout/Navigation'
import LogIn from '../components/User/LogIn'
import useFormatMessage from '../hooks/useFormatMessage'
Expand All @@ -37,6 +38,7 @@ export default function DebugPage() {
<BadgesAdmin className="DebugPage__Section" />
<SnapshotStatus className="DebugPage__Section" />
<EnvStatus className="DebugPage__Section" />
<TriggerFunction className="DebugPage__Section" />
<ErrorReporting className="DebugPage__Section" />
</Container>
)
Expand Down
5 changes: 1 addition & 4 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,13 @@ import { activateProposals, finishProposal, publishBids } from './entities/Propo
import { DiscordService } from './services/DiscordService'
import filesystem from './utils/filesystem'

const FIRST_DAY_OF_EACH_MONTH = '0 0 1 * *'
const FIFTH_OF_SEPTEMBER = '0 0 5 9 *' // TODO: remove after 05-09-2013
const jobs = manager()
jobs.cron('@eachMinute', finishProposal)
jobs.cron('@eachMinute', activateProposals)
jobs.cron('@eachMinute', publishBids)
jobs.cron('@daily', updateGovernanceBudgets)
jobs.cron('@daily', runAirdropJobs)
jobs.cron(FIRST_DAY_OF_EACH_MONTH, giveTopVoterBadges)
jobs.cron(FIFTH_OF_SEPTEMBER, giveTopVoterBadges) // TODO: remove after 05-09-2013
jobs.cron('@monthly', giveTopVoterBadges)

const file = readFileSync('static/api.yaml', 'utf8')
const swaggerDocument = YAML.parse(file)
Expand Down

0 comments on commit 7b80fe9

Please sign in to comment.