# Self Services Documentation
###### tags: `handover`
## Solution Hierarchy

## Setup Guide & Local Deployment
### Source Code
1. Make sure you have access to `https://github.com/FlairstechProductUnit/SelfServices` first.
2. Perform a `git clone` operation where you need to run locally after you've logged in into the Git Client:
```git
git clone https://github.com/FlairstechProductUnit/SelfServices.git
```
3. Perform a clean **Rebuild**, you should now be getting some errors.
4. Now we need to setup a new Nuget Package Source, open "Manage Packages for Solution" page.
5. 
6. Click tha Add Button

7. Choose a suitable name, say: FlairsIdentityShared
8. Choose a suitable build location, say: `D:/Publish/FlairsIdentityShared`
9. Open Flairs Identity Solution
10. Right click the Shared Project, then publish it to the same location `D:/Publish/FlairsIdentityShared`
11. Try to perform another rebuild, and all errors should be gone by now
### Publish SSA's Shared Library
1. Right click the Shared Project on **SSA's Solution**, then publish it to a suitable location `D:/Publish/SSAShared`
2. This path would be later used by Payroll.
### Authentication and Authorization
- By default, authentication is turned off for the **Development Environment** for smoother development experience. If you ran the solution using the `SelfService-Staging` profile, authentication would be turned on again (normal behaviour).
- To dynamically inject an existing user as the currently logged in user while debugging, add a **Break Point** on the **`testAccount`** variable on the `UserDetailsMiddleware`, and provide a **valid Organization Email**
**OR**
Edit the `"TestAccountEmail": ""` element in `appsettings.json`
- To override this behaviour while in development mode, remove these blocks
```csharp
// Startup.cs
public void Configure(•••)
{
app.UseEndpoints(endpoints =>
{
•••
if (env.IsDevelopment())
endpoints.MapControllers().WithMetadata(new AllowAnonymousAttribute());
•••
});
}
```
```csharp
// UserDetailsMiddleware.cs
public void InvokeAsync(•••)
{
•••
if (!isAuthenticated && environment.IsDevelopment())
{
var testAccount = await profileRepository.GetByOrganizationEmail(appSettingsOptions.Value.TestAccountEmail);
userDetailsProvider.Initialize(testAccount, string.Empty);
await _next(context);
return;
}
•••
}
```
### JWT Token Generation
1. Make sure you have access to `https://github.com/FlairstechProductUnit/SelfServices` first.
2. Pull down the latest Master branch for the FlairsIdentity solution
```git
git clone https://github.com/FlairstechProductUnit/IdentityServer.git
```
3. Perform a clean **Rebuild**
4. Publish the applet called "TestClient"
5. Use it for token generation for a specific account.
### Kafka Brokers
To be able to run locally without consuming the staging server's own Kafka Brokers, you need to run other set of brokers on your local machine, the best method is by using **Docker (`docker-compose`)**.
1. Save this file somewhere safe with the name: `docker-compose.yml`
```yaml
version: '2'
services:
zookeeper:
image: 'docker.io/bitnami/zookeeper:3-debian-10'
ports:
- '2181:2181'
volumes:
- 'zookeeper_data:/bitnami'
environment:
- ALLOW_ANONYMOUS_LOGIN=yes
kafka:
image: 'docker.io/bitnami/kafka:2-debian-10'
ports:
- '9092:9092'
volumes:
- 'kafka_data:/bitnami'
environment:
- KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
- KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092
- ALLOW_PLAINTEXT_LISTENER=yes
depends_on:
- zookeeper
volumes:
zookeeper_data:
driver: local
kafka_data:
driver: local
```
2. Make sure you have Docker running, open up a new terminal instance where you've saved the previous file, and run:
```dockerfile
docker-compose up
```
## Development Notes
### StateTypes
*Found under `SelfServices.Shared.Enums`*
State types are not more than an enum representing all states of a workflow, where each state has:
- An ID (coupled with database IDs and **should never be altered**)
- An Identifier (enum name)
- Display Name (for representing the state in UI through the **`Display` attribute**)
- To convert an Enum to it's display name, we use the `ToNameValue()` method under the `SelfServices.Shared.Extensions.EnumExtentions` class
**📝 NOTE:** StateTypes are found under the Shared Library of SSA to be used accross other services such as **Payroll**.
### Choices
*Found under `SelfServices.Constants`*
A choice is considered a **state selector**, a state is chosen as the next state if it's corresponding choice matches the user's provided choice.
Each choice has a selector name and a past tense representation of that choice, that appears on inside the request details after taking the action.
Each past tense is set inside the dictionary `Dictionary<string, string> PastTenses`, and converted using the function `ToPastTense(string choice)`, both of which are found under `SelfServices.Constants.Choices`.
### State Definition
State definitions are just a wrapper classes around **`StateTypes`**, they contain helper fields and methods to make using and deciding that a `StateType` should be the next state easier.
State Definition contains the following:
- The identifying `StateTypes` enum that's being wrapped
- Dictionary of Outcomes, where each outcome has a`Choice` string combined with the StateDefinition itself.
- A list of `Func<>`s for pre and post processing once stepping into the state, such as:
- **PreAction**: a defined action mainly used for sending MQ messages to other services once the workflow reaches the state.
- **ImmediateDecision**: a defined action mainly used to take an immediate decision at the end of `TakeAction()` function to step into the function once again with the next state depending on a choice..
- **RequestAssignee**: an action defined to decide who the next WorkflowAssignee should be, mainly used to assign to a preknown entity, such as an existing user or an application.
### Workflow Definition
*Found under `SelfServices.Models.WorkflowComponents`*
A workflow definition is what holds the entire cycle of the Workflow, starting with an `InitialState`.
A workflow definition consists of three main parts:
- Initial State: This is what ignites the workflow, the entrypoint of the workflow.
- List of States: A public list holding all the unique states within the workflow definition, mainly used to ensure that a **`Choice`**/**`State`** combination exists.
- **This is mainly required to check whether a user action is valid or not.**
- Type: Each definition is identified by an **enum** to differentiate it from other definitions.
### Assignee
An assignee is a generic entity identifier, which could be user, or an existing service (app in the Flairs Suite).
Any given assignee would consist of two main fields,
- Type: Either an app or user
- Identifier:
- **In case of an App**, the assignee is identified by a preset string, found under `SelfServices.Shared.Constants.CommunicatingApps`
- **In case of a user**, the field should hold the user's **organization email**.
### Audits
*Found under `SelfServices.Models.Database`*
An Audit is a database model used to preserve history of a specific workflow instance, by saving a `PreState`, a `PostState`, the chosen `Choice`, and whom it was taken by `TakenBy` (either an app or a user).
From the point of view of a user, Audits are used to calculate the whole Timeline of a given workflow instance, see `Task<List<TimelineStepResponse>> GetTimeline(string requestId)` in `WorkflowSerivce`.
### Definers
#### State Definers
*Found under `SelfServices.Core.Definers`*
Like mentioned before, a state definition is just wrapper around a `StateType` alongside some `Func`s.
**`StateDefiners`** are used to create `StateDefinition` defined objects, that can be used right away.
Definers are reusable classes so that state definitions can be shared accross different workflow defnitions.
For example:
```csharp
// Found under SelfServices.Core.Definers.States
public class PendingTargetEmployeeActionStateDefiner
{
/*
* The define function returns a new StateDefinition that
* has no PreAction, but directs the flow to assign
* the next step to the "Target Employee" using
* his organization email.
* */
public StateDefinition Define()
{
return new StateDefinition(StateTypes.PendingTargetEmployeeAction)
{
// Using Task.FromResult() because no need for
// async processing.
RequestAssignee = (instance) =>
{
var targetEmployeeEmail = instance.TargetEmployee.OrganizationEmail;
return Task.FromResult(Assignee.GetUserAssignee(targetEmployeeEmail));
},
// No preaction is required for this state.
PreAction = (instance) => Task.FromResult(true)
};
}
}
```
#### Workflow Definers
*Found under `SelfServices.Core.Definers`*
Workflows are defined in order to calculate the full `WorkflowDefinition` object in runtime, this is done using the `IWorkflowDefiner` interface which can do the following:
- Define the actual flow
- Get the type of the flow (database set enumerator)
- Get who has access to the flow using a combination of Policies and Permissions
All of these field must be set when creating a new workflow definition.
Defining the flow consists of two main steps:
1. Defining each state
2. Connecting all states together by a choice
A workflow definition is considered defined successfuly once the definer returns a new instance of `WorkflowDefinition`, with an **Initial State** **connected to other state.**
### Task Representation
#### Task Statuses
A task is simply a representation of a Workflow Instance in a specific state, where this state is pending the currently logged in user's action.
A taks could have two states **from the user's point of view**:
- `TaskStatuses.PENDING`: User has not taken action yet
- `TaskStatuses.DONE`: User has already taken action
Both these states are determined through the mapping profile, and not else where.
#### Task Notes
Task notes are simple strings to direct the user which action should they take, and how could this action affect other components in the suite.
Task notes have two levels of importances:
1. Normal

2. Important

These are set using the `SelfServices.Constants.TaskNote` helper class. This class is converted to a `TaskNoteResponse` using automapper, and attached to `SelfServices.ViewModels.Responses.Tasks.MyTasksResponse` class.
Here are all the cases where a Task Note is expected to show up:
- **State: Pending Finance Action**
Importance: Normal
"Applying this request affects social insurance and requires monthly payrolls to be open."
- **State: RePending Finance Action** (Reassigned to finance due to an external error thrown by Payroll)
Importance: Important (Red Color)
"This task was reassigned to you because Payroll blocked your previous action.
- **State: Pending HR Business Partner in Promotion Workflow**
Importance: Normal
"Applying this request requires monthly payrolls to be open."
- **State: Pending HR Business Partner in HR Letter Workflow**
Importance: Normal
"Applying this request sends the final letter to the request issuer."
### Injectables
The whole injectables framework consists of 3 main components:
1. **Component Model** describing the requirements of user input, *found under `SelfServices.Models.WorkflowComponents.Injectables.Components`*
2. **Component Description** models, forms a contract for the API endpoint to describe the model in simple object notation (easily converted to JSON through .NET), think of it as the ViewModel of injectable components
3. **Component Processors** which contains all the logic required to:
- Validate user input against all preset rules
- Parse user input from **string** to the `AffectedField`'s required data type
- Create a description of the component for the UI using preset requirements and the current `AffectedField`'s value.
Below is the UML diagram of the component models relationship:

Below is the UML diagram of the component processors:

#### `InjectableDataStateDefinition`
To be able to move to the next state within a given workflow instance, a **choice** is needed to be able to choose the correct corresponding state.
In addition to the previous, for States requiring special input from user, aka, **InjectableStates**, a check is made to make sure that there was enough data provided and that it matches the preset requirements.
```csharp
// Found inside the TakeAction function
List<FieldValuePair> injectedFieldsToBeUpdated =
(chosenOutcome is InjectableDataStateDefinition injectableDataState) ?
injectedFieldsToBeUpdated = await ApplyInjectedData(injectableDataState.InjectableComponents, injectedData) :
null;
// ApplyInjectedData ensures data was
// provided if needed and validates it
// using the ToFieldValue() function.
```
### Workflow Actor
*Found under `SelfServices.Core.Utils`*
Workflow actor is designed to act as the engine of the entire application. Its main role is to move an instance from a state to the new state while validating the procedure and sending notifications if applicable.
Here is a simple diagram explaining how the class work in brief:

## Adding a new Workflow Checklist
### Models:
- Add a new `WorkflowTypes` enum entry
- Add the child model of the WF, inheriting from base WorkflowInstance class to `SelfServices.Shared.Models.Workflows`
- Remember to provide the correct `WorkflowTypes` enum to the base class
- Remember to add `[BsonIgnoreExtraElements]` attribute to child model
- Add the `typeof()` child wf to `BsonKnownTypes`attribute in base class
- Add any new properties to `FullWorkflowInstance`
- Add an implicit operator method
- Register to `Cast()`
### Core: Definers
- Add a new *(empty)* WorkflowDefiner class
- Implement `IWorkflowDefiner` correctly,
- start with setting the workflow type property,
- `Define()` should be implemented too, start with an empty function
- Make sure to provide the required **Permissions and Policies**, *(add new if required)*
- Register the new definer in **DI**
- Copy and create a Unit Test class for the new definer
- Replace any references in the class to utilize the new definer.
- Set the test cases to match the required flow
- Run the tests, all should fail since the definer class is empty **(TDD)**
- Add `StateConnections` to **`DefineStateConnections()`**
- Add any Missing States
- If you've added a new State to the enum:
- Provide a name for the state
- Add the state to the corresponding array if it **`IsPendingUserActionState()`**
- Add state definers if needed
- Remember to assign the correct `StateTypes` enum value
- Add new choices if needed, **especially if the states requires data injection**
- Register them in DI if needed
- Re-run tests till TCs match the connections defined in the WF-Definer
### Persistance
- Register `ChildInstanceRepository<>` to DI
- Register new WF instance type in `GetById()`
- Add WF to `appsettings` to map to WorkflowInstances collection
### Core: Service & Endpoints
- Add a Core Service for the new WF
- Include a Create Method
- Add mapping to the response type
- Include methods to handle responses from another apps
- Add `MQ_HostedServices` to `Program.cs`
- *See reflecting these changes in Payroll*
- **If a new role was added**, make sure to check these methods in `WorkflowServices.Generic`
- **`HandleRoleCheckMessage`**
- **`HandleProfilesInRoleMessage`**
- **If new shared MQ methods** were added, remember to implement them in **`TestingMoq`**
- Add to DI
- Include Integration tests for core services
- Add Controller methods
- Create
- More Details
- Add the new type to `ResolveInstance()`
- *Any other required methods*
#### Reflecting new MQ queues in Payroll
- Add a Sender method to Sender Service in Infrastructure
- Add a Handler service with functions for each topic (request message)
- Add the service to DI
- Include any message mapping to Automapper's profile
- Add MQ Hosted service to utilize the handler service
- Don't forget to register it to `Program.cs`
- If a new role was added to payroll and ssa, rememeber to register it in `ConvertSSARoleName()` function in payroll
### Notification Engine
- Add the notification engine
- Remember to replace `WorkflowTypes` values to use the new type
- Add the engine to DI
- Register the engine in `NotificationEngineFactory`'s `Create()` method
### Injectables
To add a new injectable component:
1. Add Component to `SelfServices.Models.WorkflowComponents.Injectables.Components`
- Add new `InjectableComponentTypes` enum
- Provide it through the base constructor
2. Add Description to Models, only include fields specific to the new component, common fields are already available for the `BaseDescription`
3. Add a new Component Processor to `SelfServices.Core.ComponentProcessors`
- Add unit tests for the new processor, test cases could mainly be for these functions:
- Validate
- Parse
4. Register the `Processor` into **DI**
5. Provide a mock description response in Swagger Docs for the `GetInjectableComponents()` endpoint found under `SelfServices.Controllers`
### Finally, make sure to setup the following in Payroll too
* Add response viewmodel to payroll
* Add viewmodel mapping to mappingprofiles
## SelfServices Deployment Checklist
This applies to both the Staging server on `192.168.54.200` and the Live Server which is only accessible by authorized users.
### Identity Server Shared Library
If the change involves adding new **Kafka MQ Queues** or modifying existing **message models**, do the following:
- Merge with master branch
- Rebuild and Publish
### Self-Services
1. `BACKEND` Merge and push to master branch
2. `BACKEND` Rebuild
3. `BACKEND` Publish Shared Library
4. `BACKEND` Publish Endpoint
5. `BACKEND` Backup
6. `FRONTEND` Deployment Path on Staging Server
7. `FRONTEND` Backup
8. `DATABASE` Backup
9. `DATABASE` Run migration queries:
```javascript
•••
```
10. `BACKEND` Modify AppSettings:
```json
•••
```
9. `BACKEND` Replace ***(in `wwwroot`)***:
```
•••
```
10. `BACKEND` Deploy
- If the changes include updating any of the nuget packages (other than FlairsIdentity's `SharedLibrary`, be sure to replace all files of the project (execluding `wwwroot`, `appsettings`, and `web.config`)).
- If not, just replace files starting with "`SelfServices.•••`" in addition to the FlairsIdentity's `SharedLibrary`
12. `FRONTEND` Deploy