-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
workflow triggers #4
base: main
Are you sure you want to change the base?
Changes from all commits
68c170e
ee287b1
909e2a3
bec0d28
b62d843
752caa0
b09b901
b98edc9
0321889
cee0865
ec91b79
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
version: 1 | ||
|
||
name: Join test | ||
|
||
on: | ||
- workflow_dispatch | ||
- join | ||
- text: | ||
match: hello | ||
|
||
steps: | ||
- name: send text | ||
action: daab:message:text | ||
with: | ||
text: はじめまして |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -9,6 +9,7 @@ import path from 'path'; | |||||
import * as uuid from 'uuid'; | ||||||
import yaml from 'js-yaml'; | ||||||
import { Action, CustomAction, MessageAction } from './actions'; | ||||||
import { parseTrigger, isTriggerFired } from './triggers'; | ||||||
import { | ||||||
DirectUser, | ||||||
DirectTalk, | ||||||
|
@@ -19,6 +20,11 @@ import { | |||||
TaskWithResponse, | ||||||
TextMessage, | ||||||
YesNoWithResponse, | ||||||
NoteCreated, | ||||||
NoteUpdated, | ||||||
NoteDeleted, | ||||||
JoinMessage, | ||||||
LeaveMessage, | ||||||
} from './daab'; | ||||||
import { | ||||||
DefaultAction, | ||||||
|
@@ -28,6 +34,9 @@ import { | |||||
DefaultActionWith, | ||||||
isCustomAction, | ||||||
getCustomActionName, | ||||||
WorkflowEvent, | ||||||
WorkflowEventType, | ||||||
WorkflowEventWith, | ||||||
} from './workflow'; | ||||||
import { Repository } from './repository'; | ||||||
|
||||||
|
@@ -47,7 +56,7 @@ export class Workflows { | |||||
|
||||||
const docs = new Map<string, Workflow>(); | ||||||
filenames.forEach((fn) => { | ||||||
const w = yaml.load(fs.readFileSync(fn, 'utf8')); | ||||||
const w = this.parse(yaml.load(fs.readFileSync(fn, 'utf8'))); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. このタイミングで |
||||||
if (this.validate(w)) { | ||||||
docs.set(w.name, w); | ||||||
} else { | ||||||
|
@@ -57,14 +66,39 @@ export class Workflows { | |||||
return new Workflows(docs, repository); | ||||||
} | ||||||
|
||||||
static validate(obj: any): obj is Workflow { | ||||||
return typeof obj === 'object' && obj.version && obj.name && Array.isArray(obj.steps); | ||||||
static parse(w: any): Workflow { | ||||||
if (w) { | ||||||
w.on = parseTrigger(w.on); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
if (this.validate(w)) {
w.on = parseTrigger(w.on); // ← ここでは on がエラーにならない
docs.set(w.name, w);
} else { (※ |
||||||
} | ||||||
return w; | ||||||
} | ||||||
|
||||||
static validate(obj: Workflow): boolean { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. こちらは type guard 関数に戻したいです.
Suggested change
|
||||||
return ( | ||||||
typeof obj === 'object' && | ||||||
typeof obj.version === 'number' && | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (元々 |
||||||
!!obj.version && | ||||||
typeof obj.name === 'string' && | ||||||
!!obj.name && | ||||||
Array.isArray(obj.steps) && | ||||||
typeof obj.on === 'object' | ||||||
); | ||||||
} | ||||||
|
||||||
getNames(): string[] { | ||||||
return Array.from(this.docs.keys()).sort(); | ||||||
} | ||||||
|
||||||
getSelectableNames(): string[] { | ||||||
return this.filterByEvent(WorkflowEvent.WorkflowDispatch).map((workflow) => workflow.name); | ||||||
} | ||||||
|
||||||
filterByEvent(type: WorkflowEventType, e?: WorkflowEventWith): Workflow[] { | ||||||
return this.getNames() | ||||||
.map((name) => this.findByName(name)!) | ||||||
.filter((workflow) => isTriggerFired(type, workflow.on[type], e)); | ||||||
} | ||||||
|
||||||
findByName(name: string): Workflow | undefined { | ||||||
return this.docs.get(name); | ||||||
} | ||||||
|
@@ -76,6 +110,16 @@ export class Workflows { | |||||
} | ||||||
return undefined; | ||||||
} | ||||||
|
||||||
createWorkflowContextByEvent( | ||||||
type: WorkflowEventType, | ||||||
e?: WorkflowEventWith | ||||||
): WorkflowContext | undefined { | ||||||
const workflow = this.filterByEvent(type, e); | ||||||
if (workflow.length) { | ||||||
return WorkflowContext.create(workflow[0], this.repository); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
// * NOTE: Hubot の Listener 処理が Promise に対応していないため,daab-session を使わずに別途設けることになった | ||||||
|
@@ -148,6 +192,7 @@ export class WorkflowContext { | |||||
private valid = true; | ||||||
private stepIndex = 0; | ||||||
private data: WorkflowStepData = {}; | ||||||
private readonly firstStep = { id: 'on' } as WorkflowStep; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
private readonly actors = new Set<string>(); | ||||||
|
||||||
private constructor( | ||||||
|
@@ -205,7 +250,14 @@ export class WorkflowContext { | |||||
this.data = {}; | ||||||
} | ||||||
|
||||||
private resetByEvent(type: WorkflowEventType) { | ||||||
this.stepIndex = -1; | ||||||
this.data = {}; | ||||||
this.firstStep.action = `daab:message:${type.split('_')[0]}`; | ||||||
} | ||||||
|
||||||
private get currentStep() { | ||||||
if (this.stepIndex === -1) return this.firstStep; | ||||||
return this.workflow.steps[this.stepIndex]; | ||||||
} | ||||||
private get isLastStep() { | ||||||
|
@@ -231,7 +283,14 @@ export class WorkflowContext { | |||||
|
||||||
private getUserId(res: Response<any>, step: WorkflowStep) { | ||||||
const args = step.with as { to?: string }; // ! FIXME | ||||||
return this.findUserId(res, args.to)?.id ?? res.message.user.id; | ||||||
// FIXME | ||||||
const obj = (res.robot as any).direct.api.dataStore.me.id; | ||||||
const botId = `_${obj.high}_${obj.low}`; | ||||||
let userId = res.message.user.id; | ||||||
if (userId == botId) { | ||||||
userId = res.message.roomUsers.find((user: any) => user.id !== botId)?.id; | ||||||
} | ||||||
return this.findUserId(res, args.to)?.id ?? userId; | ||||||
} | ||||||
|
||||||
private async findOrCreateUserContext(userId: string) { | ||||||
|
@@ -312,6 +371,11 @@ export class WorkflowContext { | |||||
} | ||||||
} | ||||||
|
||||||
async triggerWorkflow(type: WorkflowEventType) { | ||||||
this.resetByEvent(type); | ||||||
this.activate(); | ||||||
} | ||||||
|
||||||
private async exitWorkflow() { | ||||||
this.reset(); | ||||||
this.deactivate(); | ||||||
|
@@ -330,6 +394,10 @@ export class WorkflowContext { | |||||
await this.repository.destroy(this); | ||||||
} | ||||||
|
||||||
async cancelWorkflow() { | ||||||
return this.exitWorkflow(); | ||||||
} | ||||||
|
||||||
async handleSelect(res: ResponseWithJson<SelectWithResponse>) { | ||||||
const current = this.currentStep; | ||||||
if (current.action != DefaultAction.Select) { | ||||||
|
@@ -396,4 +464,85 @@ export class WorkflowContext { | |||||
|
||||||
await this.runNextAction(res); | ||||||
} | ||||||
|
||||||
async handleNoteCreated(res: ResponseWithJson<NoteCreated>) { | ||||||
const current = this.currentStep; | ||||||
if (current.action != DefaultAction.Note) { | ||||||
return; | ||||||
} | ||||||
|
||||||
if (current.id) { | ||||||
this.data[current.id] = { | ||||||
responder: res.message.user, | ||||||
...res.json, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
response: { note: res.json }, | ||||||
}; | ||||||
} | ||||||
|
||||||
await this.runNextAction(res); | ||||||
} | ||||||
|
||||||
async handleNoteUpdated(res: ResponseWithJson<NoteUpdated>) { | ||||||
const current = this.currentStep; | ||||||
if (current.action != DefaultAction.Note) { | ||||||
return; | ||||||
} | ||||||
|
||||||
if (current.id) { | ||||||
this.data[current.id] = { | ||||||
responder: res.message.user, | ||||||
...res.json, | ||||||
response: { note: res.json }, | ||||||
}; | ||||||
} | ||||||
|
||||||
await this.runNextAction(res); | ||||||
} | ||||||
|
||||||
async handleNoteDeleted(res: ResponseWithJson<NoteDeleted>) { | ||||||
const current = this.currentStep; | ||||||
if (current.action != DefaultAction.Note) { | ||||||
return; | ||||||
} | ||||||
|
||||||
if (current.id) { | ||||||
this.data[current.id] = { | ||||||
responder: res.message.user, | ||||||
...res.json, | ||||||
response: { note: res.json }, | ||||||
}; | ||||||
} | ||||||
|
||||||
await this.runNextAction(res); | ||||||
} | ||||||
|
||||||
async handleJoin(res: Response<JoinMessage>) { | ||||||
const current = this.currentStep; | ||||||
if (current.action != 'daab:message:join') { | ||||||
return; | ||||||
} | ||||||
|
||||||
if (current.id) { | ||||||
this.data[current.id] = { | ||||||
responder: res.message.user, | ||||||
}; | ||||||
} | ||||||
|
||||||
await this.runNextAction(res); | ||||||
} | ||||||
|
||||||
async handleLeave(res: Response<LeaveMessage>) { | ||||||
const current = this.currentStep; | ||||||
if (current.action != 'daab:message:leave') { | ||||||
return; | ||||||
} | ||||||
|
||||||
if (current.id) { | ||||||
this.data[current.id] = { | ||||||
responder: res.message.user, | ||||||
}; | ||||||
} | ||||||
|
||||||
await this.runNextAction(res); | ||||||
} | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
基本的に項目の重複はないと思いますので,リストよりはハッシュ構造の方が良さそうです.
(
text
等は複数発生するかもしれませんが,それはtext
フィールド以下の属性で表現するイメージです)