Tasks are the bits of the framework which do stuff, like making a clip on twitch, writing text to a file or sending a message to chat.
Triggers are the bits that notify a flow to execute, like when a chat message is sent, or every 10 seconds etc.
The two elements are quite similar for the most part but mainly they differ in how they are used, but both contain Data, Task/Trigger and Component aspects, all are required to function, and all are needed for them to function in the system.
Task/Trigger Data
While they both differ in term of the interface they both represent the same notion which is purely the DATA required for a task/trigger to run, these classes will be what get persisted to the file system that describe a task.
// Its IFlowTaskData for Tasks and IFlowTriggerData for triggerspublicclassWriteToLogTaskData:IFlowTaskData{ // This is the contract between the data class and the logic classpublicstaticreadonlystring TaskCode ="write-to-log"; // This is the version of your data, which should increment as you change itpublicstaticreadonlystring TaskVersion ="1.0.0"; // The unique id for the INSTANCE of the task datapublicGuid Id { get; set; } =Guid.NewGuid(); // This is the code but at instance levelpublicstring Code => TaskCode; // This is the instances version (could be different to the static version)publicstring Version { get; set; } = TaskVersion; // These are the properties you want for your task logic to usepublicstring Text { get; set; }}
As you can see there isnt much too it, the TaskCode and TaskVersion are static because we reference them within the relevant task or trigger, the id, code, version instance properties are there to describe the task/trigger in the system, then finally the last section (the Text property in this example) can contain any fields of data you need to persist for this to work.
You can have as many or little properties as you need, but NO LOGIC should exist within here as that lives in the ACTUAL task/trigger, ultimately anything in here just gets serialized into json into the flow data file.
Creating Task Logic
// We inherit from FlowTask<TaskDataTypeClass> for taskspublicclassWriteToLogTask:FlowTask<WriteToLogTaskData>{ // As you can see we reference the static property on the task data classpublicoverridestring Code =>WriteToLogTaskData.TaskCode; // Same with versionpublicoverridestring Version =>WriteToLogTaskData.TaskVersion; // This is what should be shown on the task name in the UIpublicoverridestring Name =>"Write To Log"; // This is the category grouping it should have in the UIpublicoverridestring Category =>"Utility"; // This is the tooltip text that will come up in the UIpublicoverridestring Description =>"Writes the text out to the log file, useful for debugging"; // By default you will get some dependencies injected for free from the base class, but you can // add anything else you want to the constructor as long as its been registered in the DI configurationpublicWriteToLogTask(ILogger<FlowTask<WriteToLogTaskData>> logger,IFlowStringProcessor flowStringProcessor,IAppState appState,IEventBus eventBus) : base(logger, flowStringProcessor, appState, eventBus) { } // This indicates if the task can be run, i.e for twitch you may not have a // valid oauth token, or valid scopes for the given task, so it shouldnt runpublicoverrideboolCanExecute() =>true; // This is the core execution part where you do the real work, you are given the data instance // and the variables for the current flow, you should return a true for success or false for failurepublicoverrideasyncTask<bool> Execute(WriteToLogTaskData data,IVariables flowVars) {var processedText =FlowStringProcessor.Process(data.Text, flowVars);Logger.Information(processedText);returntrue; }}
While there is a bit of stuff there its not that complex really, you just put whatever you want to do into the Execute method and whenever a flow uses this task it will run that execute method with its given flow state.
There will only ever be one instance of the task in the app at once, so each flow will call Execute with its own state, so keep that in mind that ALL state needed to execute needs to be included in the data object or flow variables.
Creating Trigger Logic
// Same as task really, we just inherit from FlowTrigger<TriggerDataObjectType>publicclassOnEventRaisedTrigger:FlowTrigger<OnEventRaisedTriggerData>{ // The code from the trigger data classpublicoverridestring Code =>OnEventRaisedTriggerData.TriggerCode; // Same with versionpublicoverridestring Version =>OnEventRaisedTriggerData.TriggerVersion; // Any variables this trigger should expose (can also be added to tasks too)publicstaticVariableEntry EventDataVariable =new("event-data");publicoverrideVariableDescriptor[] VariableOutputs { get; } =new[] { EventDataVariable.ToDescriptor() }; // The name of the trigger to show in the UIpublicoverridestring Name =>"On Event Raised"; // The category the trigger should be in on the UIpublicoverridestring Category =>"Utility"; // The tooltip to show in the UIpublicoverridestring Description =>"Triggers when the matching event is raised"; // Much like tasks you get a lot of dependencies for freepublicOnEventRaisedTrigger(ILogger<FlowTrigger<OnEventRaisedTriggerData>> logger,IFlowStringProcessor flowStringProcessor,IAppState appState,IEventBus eventBus) : base(logger, flowStringProcessor, appState, eventBus) { } // Like tasks indicates if this trigger should be runnablepublicoverrideboolCanExecute() =>true; // Returns an observable with the variables for this flow, takes in the data object for statepublicoverrideasyncTask<IObservable<IVariables>> Execute(OnEventRaisedTriggerData data) {returnEventBus.Receive<UserDataEvent>() .Where(x =>x.EventName==data.EventName) .Select(x => {var newVariables =newVariables();newVariables.Set(EventDataVariable,x.Data);return newVariables; }); }}
As you can see its VERY similar to a task, its just our Execute returns an IObservable which contains the variables for the flow.
If you are unsure what an IObservable is, go look into Reactive Extensions online.
Task/Trigger Components
// Inherit from the CustomTaskComponent (or CustomTriggerComponent) with the data object type for the generic @inherits CustomTaskComponent<Strem.Flows.Default.Flows.Tasks.Utility.WriteToLogTaskData>// Put whatever you want in here, its what the user will see inside the Task/Trigger section<divclass="field"> <labelclass="label">Text To Log</label> <divclass="control"> <inputclass="input"type="text"placeholder="i.e Something Happened With Value V(someVar)"@bind="Data.Text"> </div></div>// Have any custom logic for your component in here, the mandatory Title is what is shown in the UI@code { public override string Title => "Write Text To Logs";}
These are basically just Blazor components inheriting from an abstract class, this keeps things consistent in terms of what data and properties will be expected, it also helps the app work out what components need to be hosted within the app.
You cannot provide any [Parameter] properties to task/trigger components as they are loaded dynamically at runtime and get their data through the abstract class initialization. This being said you can @inject ISomeDependency Dependency as much as you want to access other objects registered within the DI configuration.