Skip to content

Commit

Permalink
Updating project documentation (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
kryvyifedir authored Oct 28, 2023
1 parent 4561414 commit 2d273b8
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 0 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,13 @@ Check our [Contribution Guidelines](docs/CONTRIBUTION.md) for more information o
Production version of GameForce is distributed through Salesforce AppExhange. It can be found under the link:
Please see [Configuration Guide](docs/CONFIGURATION.md)

## Project documentation
Project documentation contains information about GameForce technical details and approaches. We strongly recommend to check-it out before contributing to a project, since it might save you some time.

Project documentation includes:
- [Aura LWC wrappers](/docs/AURA.md)
- [Apex guidelines](/docs/CLASSES.md)
- [GameForce data schema](/docs/OBJECTS.md)
- LWC Components **WIP**
- [Permission sets and groups](/docs/PERMISSIONSETSANDGROUPS.md)
- [Communicating with Platform events](/docs/PLATFORMEVENTS.md)
13 changes: 13 additions & 0 deletions docs/AURA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Aura LWC wrappers
Aura components are used in the project as an exception and only in cases when Aura wrapper is necessary for LWC due to some of the API not being ported to LWC yet. For example - `GameForceUtility` aura compoenent is a wrapper for `gameForceNotification` LWC component due to the fact that `lightning:utilityBarAPI` isn't available in LWC yet.

## GameForceUtility
### General information
`GameForceUtility` is an Aura wrapper for `gameForceNotification` LWC component. It is necessary because `lightning:utilityBarAPI` API which is available in Aura components wasn't ported yet to LWC.

### Technical design
`GameForceUtility` is built around two APIs: `lightning:empApi` and `lightning:utilityBarAPI`
- `lightning:empApi` is used to intercept [`AchievementReached__c`](../force-app/main/default/objects/AchievementReached__e/) platform events and to open the utility bar component without user input in case the intercepted event includes information about current user. See the [`GameForceUtility.cmp onInit()`](../force-app/main/default/aura/GameForceUtility/GameForceUtilityController.js) method for more information
- `lightning:utilityBarAPI` is used to open the utility bar component automatically when new event is received and includes data relevant for current user

All data processing and rendering is done in a child [gameForceNotification](../force-app/main/default/lwc/gameForceNotification/) LWC component
148 changes: 148 additions & 0 deletions docs/CLASSES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Apex guidelines
All the backend logic of GameForce is done using Apex Classes. Low-code tools are not used for development.
This section is meant to explain some key concepts that are utilized across the GameForce code.

## Using BaseSelector class to retrieve data instead of direct SOQL request
GameForce relies on [`BaseSelector`](../../force-app/main/default/classes/BaseSelector.cls) class to retrieve data for different sObjects. Concreate realizations of the BaseSelector class are used to retrieve data for a specific sObject

To create a concrete realization of a BaseSelector class, child class has to realize 3 methods:
- `public String sObjectApiName()`: this method should return the API name of the sObject
- `public override Set<String> fieldApiNames()`: returns a list of field API names that will be retrieved by Selector class

`BaseSelector` class provides a set of methods that can be used to retrieve data without the need to create additional methods on the concrete selector class:
### getByFieldValue(String filterFieldApiName, String compOperator, Object values)
```
public List<sObject> getByFieldValue(String filterFieldApiName, String compOperator, Object values)
```

Parameters:
- `filterFieldApiName`: API name of the field that is going to be used for filtering SOQL request
- `compOperator`: Comparison operator for data filtering
- `values`: a value (set of values) that will be used for filtering data

Example:
```
getByFieldValue('Name', 'IN', new Set<String>{'Name 1', 'Name 2', 'Name 3'})
```
equals to this SOQL
```
Set<String> names = new Set<String>{'Name 1', 'Name 2', 'Name 3'}
[...WHERE Name IN :names]
```

### getByIds(Set<Id> ids)
```
public List<sObject> getByIds(Set<Id> ids)
```
Parameters:
- `ids`: API name of the field that is going to be used for filtering SOQL request

Example:
```
getByIds(new Set<Id>{'Id1', 'Id2', 'Id3'})
```
equals to this SOQL
```
Set<Id> ids = new Set<Id>{'Id1', 'Id2', 'Id3'}
[...WHERE Id IN :ids]
```

### getAll()
```
public List<sObject> getAll()
```

Example:
```
getAll()
```
equals to this SOQL
```
[SELECT ... FROM ... ]
```
This method should be used with caution since it doesn't have any limitation and can result in SOQL query limit

## Using Logger class to save information about exceptions
`Logger` class is used to save information about issues that might have occured in a system. This class will be refined in future to allow storing more granular information about the issues.

There are two main method of a Logger class that are used for storing runtime issues:
### saveSingleLog(String log)
```
public static void saveSingleLog(String log)
```
Stores a single issue in a `Log__c` sObject. Asyncronous and can be called from cached LWC methods.
Example:
```
} catch (Exception e) {
Logger.saveSingleLog('Error message');
}
```
### addLog(String log)
```
public void addLog(String log)
```
Adds a new log record, but doesn't commit changes. `commitChanges()` has to be called once all issues are collected

## Using ControllerResponse class to pass result of Apex controller to LWC components
`ControllerResponse` class is used to wrap the result of the execution of backend controller and have a more control over the issues (expected and unexpected) that might occure during the backend controller execution.

There are 3 methods that should be used to return result of a backend controller execution in a form of `Map<String, Object>`:

### success(Object obj)
```
public static Map<String, Object> success(Object obj)
```
Example:
```
@AuraEnabled(cacheable=true)
public static Map<String, Object> method(){
...
return ControllerResponse.success('value to pass to LWC');
}
```

### error(String msg)
```
public static Map<String, Object> error(String msg)
```
Example:
```
@AuraEnabled(cacheable=true)
public static Map<String, Object> method(){
Map<String, Object> result = new Map<String, Object>();
try {
...
return ControllerResponse.success('value to pass to LWC');
} catch (Exception e) {
...
result = ControllerResponse.error('error message displayed to user');
}
return result;
}
```

### warning(Object msg)
```
public static Map<String, Object> warning(Object msg)
```
Example:
```
@AuraEnabled(cacheable=true)
public static Map<String, Object> method(){
Map<String, Object> result = new Map<String, Object>();
try {
if (all good) {
return ControllerResponse.success('value to pass to LWC');
} else {
return ControllerResponse.success('Handled issue');
}
} catch (Exception e) {
...
result = ControllerResponse.error('error message displayed to user');
}
return result;
}
```

## Trigger Handlers
TBD
50 changes: 50 additions & 0 deletions docs/OBJECTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# GameForce data schema
This section is meant to explain some key concepts related to data storage and relathionships between sObjects in GameForce.

## Updating data in sObjects
One key concept is that needs explanation is that in GameForce all the data should be either changed from the UI (by GameForce [admins](PERMISSIONSETSANDGROUPS.md)), or indirectly by firing a specific [platform event](PLATFORMEVENTS.md).

## Measurement__c and UserMeasurement__c sObjects
`Measurement__c` sObjects stores metrics that are tracked for each individual user, like "Number of converted leads". Specific values per each user are stored in `UserMeasurement__c` sObject.
```mermaid
classDiagram
Measurement__c <|-- UserMeasurement__c
User <|-- UserMeasurement__c
class Measurement__c{
+UniqueIdentifier__c
+Description__c
}
class UserMeasurement__c{
+Key__c, formula: User__c + Measurement__c
+Measurement__c, lookup(Measurement__c)
+User__c, lookup(User)
Value__c
}
class User{
+Id
}
```

## Achievement__c sObject
`Achievement__c` sObject stores achievements that are related to specific measurements. Achievement is considred to be reached by user once the value in a corresponding `UserMeasurement__c` record reaches or becomes greater then the value in `Achievement.Goal__c` sObject

```mermaid
classDiagram
Measurement__c <|-- UserMeasurement__c
Measurement__c <|-- Achievement__c
class Measurement__c{
+UniqueIdentifier__c
+Description__c
}
class UserMeasurement__c{
+Key__c, formula: User__c + Measurement__c
+Measurement__c, lookup(Measurement__c)
Value__c
}
class Achievement__c{
+Measurement__c, master-detail(Measurement__c)
+Goal__c
}
```

This comparison is executed each time `UserMeasurement__c` is created or changed. [`AchievementReachedEventsManager`](../force-app/main/default/classes/AchievementReachedEventsManager.cls) does the comparison and checks if achievement was already reached by the user.
12 changes: 12 additions & 0 deletions docs/PERMISSIONSETSANDGROUPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Permission Sets and Groups
GameForce relies on on two main permission set groups to give users access to the functionality:

## GameForce Admin
Gives full access to a GameForce app including "Measurements" and "Achievements" tab so that admin users can setup new measurements and/or achievements

NOTE: This permission set is meant to be assigned to user during the development of new functionality

## GameForce User
Gives access to main functionality of GameForce but doesn't include access to configuration of the achievements and measurements

NOTE: All new functionality has to be tested using the user with this permission set group to make sure that regular users can properly use the GameForce
20 changes: 20 additions & 0 deletions docs/PLATFORMEVENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# GameForce Platform events
GameForce relies on platfomr events to track changes in user measurements as well as notifying users about unlocked achievements. Platform events are used as a means for integration with the logic outside of the GameForce and obfuscate complex data changes/validations that are required for GameForce to properly track users progress

## AchievementReached__e
`AchievementReached__e` platform event is fired whenever user reaches a specific achievement.

Payload:
- `AchievementId__c`: Id from `Achievement__c` sObject
- `UserId__c`: User Id

Whenever `AchievementReached__e` platform event is fired, `GameForceUtility` aura component handles the event, and shows a pop-up notification to a user, in case his user id matches the one in eent payload.
Also, `AchievementReachedTrigger` fires a logic that stores the information about reached achievement in `AchievementReached__c` sObject.

## UserMeasurementIncrement__e
`UserMeasurementIncrement__e` platform event is fired whenever there has to be a change of user metric. It is meant to be fired by sObject triggers for a standard list of achievements, or by flows/triggers in case custom achievements are set in a system.

Payload:
- `MeasurementId__c`: Id from `Measurement__c` sObject that needs to be changed
- `UserId__c`: User Id
- `Increment__c`: numeric value that needs to be added/removed from a `UserMeasurement__c` sObject record

0 comments on commit 2d273b8

Please sign in to comment.