π§° 1. Prerequisites
You need to have a basic understanding of JavaScript
, Node.js
, and NPM
to continue.
We recommend to use NVM.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
Check GitHub repository to verify any change in the installation process.
This project has a .nvmrc
file containing the node version number supported and tested. Check the repository to configure your terminal.
Calling nvm use
automatically in a directory with a .nvmrc
file. If it finds it, it will switch to that version; if not, it will use the default version.
nvm install <NODE_VERSION_ON_NVMRC_FILE>
nvm use <NODE_VERSION_ON_NVMRC_FILE>
π§ 2. Understand your workspace
Run: npm run dep-graph
to see a diagram of the dependencies of your projects.
π 4. Run project
You can run this template using the following commands in a different console.
To run both frontend and backend:
npm run start
Client only
npm run start:client
API only
npm run start:api
π 5. Build
nx build cloud
to build the project. The build artifacts will be stored in the dist/
directory. Use the --prod
flag for a production build.
π 6. Deploy
npm install -g serverless@<CHECK_VERSION_AT_buildspec.yml_FILE>
π§ͺ 7. Testing
nx test cloud
to execute the unit tests via Jest.
npm run affected:test
to execute the unit tests affected by a change.
By default, Nx uses Cypress to run E2E tests.
Start Cypress with
nx e2e {appName}-e2e --watch
to execute the end-to-end tests in Interactive Watch Mode.
Run e2e tests for the applications affected by changes.
npm run affected:e2e
Run the respective .spec Change files in your app, Cypress should re-run its tests.
π 8. Authentication
Kleeen Software provides the option to extend the default authentication or implements new ones.
To support custom workflows, @kleeen/auth library
exposes a set of types and interfaces.
import { Integrations, KSAuth } from '@kleeen/auth';
KSAuth.configure({
authenticationHandler: new Integrations.CognitoAuthenticationHandler(),
});
IAuthenticationHandler interface is the blueprint to implement different workflows.
Here is an example of a custom implementation:
import 'firebase/auth';
import firebase from 'firebase/app';
import { Integrations } from '@kleeen/auth';
/* Your web app's Firebase configuration */
const firebaseConfig = {
apiKey: '',
authDomain: '',
databaseURL: '',
projectId: '',
storageBucket: '',
messagingSenderId: '',
appId: '',
};
export class FirebaseAuthenticationHandler extends Integrations.AuthenticationHandler {
constructor(config: typeof firebaseConfig = firebaseConfig) {
super();
/* Initialize Firebase */
firebase.initializeApp(firebaseConfig);
}
/* Sign in a register user using username and password */
async signIn(options: SignInOptions): Promise<IUser> {
const { password, username } = options;
const response = await firebase.auth().signInWithEmailAndPassword(username, password);
return {
...response,
email: response?.user?.email,
getUsername: () => response?.user?.displayName,
role: null, // Set here the default for the current user
roles: [], // set here the list of roles assigned for the current user
};
}
/**
A function that takes a new context object and update it if needed
@param {Record<string, any>} context is an existing context
@return {Record<string, any>} with an updated context
**/
setContext(context: Record<string, any>): Record<string, any> {
return {
...context,
headers: {
...context.headers,
MY_CUSTOM_HEADER: 'GOING HERE',
},
};
}
}
Following is the example of configuring the KSAuth class to use the custom implementation.
import { FirebaseAuthenticationHandler } from './google-firebase';
import firebaseConfiguration from './custom-implementations/firebase.json';
KSAuth.configure({
authenticationHandler: new FirebaseAuthenticationHandler(firebaseConfiguration),
});
Run
nx test auth
to execute the unit tests via Jest.
To connect a login to the FE app, it's needed to implement an AuthenticationHandler like libs/auth/src/lib/integrations/aws-cognito/aws-cognito.ts
, that is the one used in our own prototypes.
When implementing this 'authenticator,' the currentAuthenticatedUser function needs to return a shape like { ...anyUserInfoNeeded, role: 'ADMIN' }
, role its required, but if it's not provided, the access-control did not interfere with anything.
- the role values depend on what the
apps/cloud/src/app/settings/role-access-keys.json
have on the permissions and can be any string. - The role-access-keys.json is created for our generated UI proposes grouping each page into NAVIGATION key and next each page have WIDGETS, VIEWS and can have more specific components also can be extended but to also reflect access on the UI, the AccessControl component is needed.
- the UI implementation follows the rules and uses the access-manager module from the ks-ui-react.
π« 9. Onboarding integration
onboarding settings = apps/cloud/src/app/modules/generated/components/on-boarding/on-boarding.settings.ts
The condition used to decide if the onBoarding shows is a combination between the onboarding settings (because the feature itself can be off) and the onboarding preferences redux state, the path in the redux state used for getting if the onBoarding
should show is endUserPreference
.onBoardingPreferences.showOnBoarding
. That onBoardingPreferences
is one of the props receive in the component below and can be manipulated with the response of the query getOnboardingPreferences
(needs to be implemented in custom API).
getOnboardingPreferences
: is called at the start of onboarding
, it filled the onBoardingPreferences
state with the response so can be used to inject data to this custom component or disable the onboarding for certain users or cases.
setOnBoardingPreference
: is call with the action preferencesActions.setOnBoardingPreference
and it can be used to store some data related to onBoarding
or set info related to user like turn off next onboarding
for the same user.
With the action preferencesActions.setOnBoardingPreference
you can change the onBoardingPreferences
state and it also calls a BE
query setOnBoardingPreference
(needs to be implemented in custom API) to save preferences for the onboarding
(for example setting the flag in real BE
to false so the user does not get the onboarding a second time).
Some of the queries that the onboarding
throws does not have a BE
query the catch it, all the firm and shape is defined on the FE
but it needs to be added to the custom API schema
and resolvers y order to getting it on the GraphQL
middleware.
example on apps/api/src/graphql/custom/operations/custom-schema.ts
export const customSchema = gql`
extend type Query {
getOnboardingPreferences: OnboardingPreferences
setOnboardingPreferences(input: PreferencesInput): OnboardingPreferences
}
`;
on apps/api/src/graphql/custom/operations/custom-resolvers.ts
export const customResolvers: IResolvers = {
Query: {
getOnboardingPreferences: () => ({ showOnBoarding: true }),
setOnboardingPreferences: (input) => ({ success: true }),
},
};
Please refer to the official documentation about how to add your custom schema.
For more information please visit Apollo GraphQL Documentation
All in these files can change except the shape and name of the export component.
π 10. Custom provider integration
As End-Developers, it is quite common that you need to share some data, and that data needs to be accessible by many components at different nesting levels inside your application. Also, you might need to integrate third party tools which should be enabled at application level, and not at workflow level.
In the path apps/cloud/src/app/modules/custom/providers/
you can add any custom global provider you need, for example you can use a higher order component pattern to wrap the whole application with your custom global provider; In this case, let's integrate a third-party tool like Intercom, using the react-use-intercom module:
Add a new file called intercom-provider.ts
, then add the following higher order component:
import { IntercomProvider } from 'react-use-intercom';
import { environment } from '../../../../environments/environment';
const intercomAppId = 'your-intercom-app-id';
interface IntercomProviderProps {
children: JSX.Element; // This represents the whole application
}
// This is the way to wrap the whole application with your Custom Global Provider
export function CustomIntercomProvider({ children }: IntercomProviderProps) {
return <IntercomProvider appId={intercomAppId}>{children}</IntercomProvider>;
}
For those reasons, Kleeen provides an easy way to add Custom Global Providers to your application, so you can fulfill those requirements, and more simply going to apps/cloud/src/app/modules/custom/providers/index.ts
file, there you have this custom barrel:
export const CustomProviders = [];
Import and add the new Custom Global Provider that we just did to the Custom Providers catalog:
import { CustomIntercomProvider } from './intercom-provider';
export const CustomProviders = [CustomIntercomProvider];
π 11. Initialize third party libraries
As End-Developers, it is common that you need to call or initialize third-party libraries on top of the react chain.
In the path apps/cloud/src/app/modules/custom/third-parties/third-party-initialize.ts
you can add any function or hook to the thirdPartyInitialize
array, so we can initialize Intercom as a custom third party as it is in the example below:
import { useIntercom } from 'react-use-intercom';
export const thirdPartyInitialize = [useIntercom];
π 12. Quick Links
As End-Developers, it is common that you need to have direct links to important source files so that you don't have to hunt for those files.
You can find your project files:
π 13. Quick Start
As an End Developer you may need to have a quick start guide on how to run the Kleeen Project, please follow the next steps:
The first step is to have the latest version of dependencies run: npm install
After the dependencies have been installed it is needed to have the Backend and the Frontend running in separate terminals
Client only
npm run start:client
In the package.json file you can specify the port the Front End is going to run for this script, by adding the option --port=<PORT_NUMBER>
for example --port=4201
.
API only
API will run by default in port 3002
npm run start:api
If needed you can also have the Backend and Frontend running in the same terminal
Backend and Frontend
npm run start
In your browser go to localhost:4200 (or your default port number)
Congratulations!
At this point your Kleeen project should be up and running.
πΊοΈ 14. System Architecture
Inside these folders are both the visualization code and the api code. The structures where the elements of both the front-end and the back-end are located are presented below.
Here are the jsons with the construction of the filter references for each of the views.
Here is the logic of the requests to the back-end. Inside this folder there are several folders, which have the request scripts.
Here are the queries and mapping of the api to which they are going to be consumed.
Inside here is part of the research interaction. This code is static and is not modified by the user, it is part of the kleeen implementation.
Here are the code generation for custom views. These are divided by folders which have the name of your workflow. Inside each of the folders there are files of type js. Which have unique names. In order to know which custom each file corresponds to, you must visit the "Generated" files and within this folder there must be a workflow with the same name. In there, there is a settings file and look for the document called 'widgets.ts', in there look for the custom widget whose code you want to change. When it is found, see the name and later look for it inside the custom folder and inside the folder with the same name in the workflow. In this section a standard code is created, which must be manipulated by the developer, to include their own code.
Here all the views that a user can access are generated. For each of the views a folder is generated with the name of the workflow, which is the name of the url to which we are going to access. Inside each folder there is a file index.jsx, which has auto-generated code and there is also a folder with the name of settings which can have different files. These files internally have a configuration json which tells our end-product what to display.
In this part of the code there are global settings, such as the 'translate', which is auto-generated and has the texts to be displayed depending on the selected language. Also the roles or accesses that a group of users can have.
Made with β€οΈ by Kleeen Software 2022