mirror of
https://fast.feibisi.com/https://github.com/parcelvoy/platform.git
synced 2025-08-28 11:46:02 +08:00
Allow for scheduling journey entrances by hour (#615)
This commit is contained in:
parent
729397907d
commit
e14155baa1
8 changed files with 35 additions and 16 deletions
|
@ -21,7 +21,7 @@ export default class Journey extends Model {
|
|||
name,
|
||||
published: true,
|
||||
})
|
||||
const { steps, children } = await setJourneyStepMap(journey.id, stepMap)
|
||||
const { steps, children } = await setJourneyStepMap(journey, stepMap)
|
||||
return { journey, steps, children }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ router.get('/:journeyId/steps', async ctx => {
|
|||
})
|
||||
|
||||
router.put('/:journeyId/steps', async ctx => {
|
||||
const { steps, children } = await setJourneyStepMap(ctx.state.journey!.id, validate(journeyStepsParamsSchema, ctx.request.body))
|
||||
const { steps, children } = await setJourneyStepMap(ctx.state.journey!, validate(journeyStepsParamsSchema, ctx.request.body))
|
||||
ctx.body = await toJourneyStepMap(steps, children)
|
||||
})
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import Journey, { JourneyParams, UpdateJourneyParams } from './Journey'
|
|||
import { JourneyStep, JourneyEntrance, JourneyUserStep, JourneyStepMap, toJourneyStepMap, JourneyStepChild } from './JourneyStep'
|
||||
import { createTagSubquery, getTags, setTags } from '../tags/TagService'
|
||||
import { User } from '../users/User'
|
||||
import { getProject } from '../projects/ProjectService'
|
||||
|
||||
export const pagedJourneys = async (params: PageParams, projectId: number) => {
|
||||
const result = await Journey.search(
|
||||
|
@ -107,15 +108,16 @@ export const getJourneyStepMap = async (journeyId: number) => {
|
|||
return toJourneyStepMap(steps, children)
|
||||
}
|
||||
|
||||
export const setJourneyStepMap = async (journeyId: number, stepMap: JourneyStepMap) => {
|
||||
export const setJourneyStepMap = async (journey: Journey, stepMap: JourneyStepMap) => {
|
||||
return await App.main.db.transaction(async trx => {
|
||||
|
||||
const [steps, children] = await Promise.all([
|
||||
getJourneySteps(journeyId, trx),
|
||||
getJourneyStepChildren(journeyId, trx),
|
||||
getJourneySteps(journey.id, trx),
|
||||
getJourneyStepChildren(journey.id, trx),
|
||||
])
|
||||
|
||||
const now = new Date()
|
||||
const project = await getProject(journey.project_id)
|
||||
|
||||
// Create or update steps
|
||||
for (const [external_id, { type, x = 0, y = 0, data = {}, data_key, name = '' }] of Object.entries(stepMap)) {
|
||||
|
@ -126,7 +128,7 @@ export const setJourneyStepMap = async (journeyId: number, stepMap: JourneyStepM
|
|||
let next_scheduled_at: null | Date = null
|
||||
if (type === JourneyEntrance.type && data.trigger === 'schedule') {
|
||||
if (step.data?.schedule !== data.schedule) {
|
||||
next_scheduled_at = JourneyEntrance.fromJson({ data }).nextDate(now)
|
||||
next_scheduled_at = JourneyEntrance.fromJson({ data }).nextDate(project?.timezone ?? 'UTC', now)
|
||||
} else {
|
||||
next_scheduled_at = step.next_scheduled_at
|
||||
}
|
||||
|
@ -137,7 +139,7 @@ export const setJourneyStepMap = async (journeyId: number, stepMap: JourneyStepM
|
|||
: await JourneyStep.insertAndFetch({
|
||||
...fields,
|
||||
external_id,
|
||||
journey_id: journeyId,
|
||||
journey_id: journey.id,
|
||||
type,
|
||||
}, trx),
|
||||
)
|
||||
|
|
|
@ -149,7 +149,7 @@ export const duplicateJourney = async (journey: Journey) => {
|
|||
const params: Partial<Journey> = pick(journey, ['project_id', 'name', 'description'])
|
||||
params.name = `Copy of ${params.name}`
|
||||
params.published = false
|
||||
const newJourneyId = await Journey.insert(params)
|
||||
const newJourney = await Journey.insertAndFetch(params)
|
||||
|
||||
const steps = await getJourneyStepMap(journey.id)
|
||||
const newSteps: JourneyStepMap = {}
|
||||
|
@ -165,7 +165,7 @@ export const duplicateJourney = async (journey: Journey) => {
|
|||
children: step.children?.map(({ external_id, ...rest }) => ({ external_id: uuidMap[external_id], ...rest })),
|
||||
}
|
||||
}
|
||||
await setJourneyStepMap(newJourneyId, newSteps)
|
||||
await setJourneyStepMap(newJourney, newSteps)
|
||||
|
||||
return await getJourney(newJourneyId, journey.project_id)
|
||||
return await getJourney(newJourney.id, journey.project_id)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz'
|
|||
import Rule from '../rules/Rule'
|
||||
import { check } from '../rules/RuleEngine'
|
||||
import App from '../app'
|
||||
import { RRule } from 'rrule'
|
||||
import { rrulestr } from 'rrule'
|
||||
import { JourneyState } from './JourneyState'
|
||||
import { EventPostJob, UserPatchJob } from '../jobs'
|
||||
import { exitUserFromJourney, getJourneyUserStepByExternalId } from './JourneyRepository'
|
||||
|
@ -122,13 +122,13 @@ export class JourneyEntrance extends JourneyStep {
|
|||
this.schedule = json?.data?.schedule
|
||||
}
|
||||
|
||||
nextDate(after = this.next_scheduled_at): Date | null {
|
||||
nextDate(timezone: string, after = this.next_scheduled_at): Date | null {
|
||||
|
||||
if (this.trigger !== 'schedule' || !after) return null
|
||||
|
||||
if (this.schedule) {
|
||||
try {
|
||||
const rule = RRule.fromString(this.schedule)
|
||||
const rule = rrulestr(this.schedule, { tzid: timezone })
|
||||
|
||||
// If there is no frequency, only run once
|
||||
if (!rule.options.freq) {
|
||||
|
@ -138,7 +138,7 @@ export class JourneyEntrance extends JourneyStep {
|
|||
return rule.options.dtstart
|
||||
}
|
||||
|
||||
return RRule.fromString(this.schedule).after(after)
|
||||
return rrulestr(this.schedule, { tzid: timezone }).after(after)
|
||||
} catch (err) {
|
||||
App.main.error.notify(err as Error, {
|
||||
entranceId: this.id,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import App from '../app'
|
||||
import { getProject } from '../projects/ProjectService'
|
||||
import { Job } from '../queue'
|
||||
import { JourneyEntrance, JourneyStep } from './JourneyStep'
|
||||
import ScheduledEntranceJob from './ScheduledEntranceJob'
|
||||
|
@ -19,15 +20,16 @@ export default class ScheduledEntranceOrchestratorJob extends Job {
|
|||
.whereJsonPath('journey_steps.data', '$.multiple', '=', true)
|
||||
.whereNotNull('journey_steps.next_scheduled_at')
|
||||
.where('journey_steps.next_scheduled_at', '<=', new Date()),
|
||||
)
|
||||
) as Array<JourneyEntrance & { project_id: number }>
|
||||
|
||||
if (!entrances.length) return
|
||||
|
||||
const jobs: Job[] = []
|
||||
for (const entrance of entrances) {
|
||||
|
||||
const project = await getProject(entrance.project_id)
|
||||
await JourneyStep.update(q => q.where('id', entrance.id), {
|
||||
next_scheduled_at: entrance.nextDate(),
|
||||
next_scheduled_at: entrance.nextDate(project?.timezone ?? 'UTC'),
|
||||
})
|
||||
|
||||
if (entrance.list_id) {
|
||||
|
|
|
@ -127,6 +127,16 @@ export default function RRuleEditor({ label, onChange, value }: RRuleEditorProps
|
|||
setValues({ ...options, until: value ? date : null })
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
name="hour"
|
||||
label="Hour (24hr Format)"
|
||||
type="number"
|
||||
min={1}
|
||||
max={24}
|
||||
required
|
||||
value={Number(options.byhour ?? 0)}
|
||||
onChange={byhour => setValues({ ...options, byhour })}
|
||||
/>
|
||||
<TextInput
|
||||
name="interval"
|
||||
label="Interval"
|
||||
|
|
|
@ -106,6 +106,11 @@ export const entranceStep: JourneyStepType<EntranceConfig> = {
|
|||
const rule = RRule.fromString(schedule)
|
||||
if (rule.options.freq) {
|
||||
s = rule.toText()
|
||||
if (rule.options.freq === RRule.DAILY) {
|
||||
s += Number(rule.options.byhour) < 12
|
||||
? 'am'
|
||||
: 'pm'
|
||||
}
|
||||
} else {
|
||||
s = 'once'
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue