@equinor/echo-core
v0.9.13
Published
![logo](https://raw.githubusercontent.com/equinor/EchoCore/main/doc/ee.png)
Downloads
830
Maintainers
Keywords
Readme
EchoCore
Everything a Echo app needs to communicate with the core.
This publishable library was generated with Nx.
How to develop and release EchoCore
Check the readme in the NX libraries folder.
Available NPM scripts
lint-core
build-core
test-core
Echo Modules
Register application components with Setup
Echo is built up around modules, a module can consist of several applications, It is recommended to keep modules as small as possible preventing long loading times at application/client (Echo) startup. All modules are registered using a setup function exported from the modules entry point. This function is called at Echo startup. The function is called with the EchoModuleApi
.
Register App
The api allows for app registration using registerApp
as shown below. App registration is used for registration of the main application in a module, based on metadata taken from the modules manifest, the optional parameter can help with configuring the application.
Register with Key
If a module consist of several apps the second app can be registered with the registerAppWithKey
function.
this lets you manually register an app, so remember some configuration is needed here.
Panels
registerPanels
is for registering panels with a provided key. This key must correspond to a path which is registered. The path is used to trigger the loading ot the panels automatically. if not it need to be triggered by the setActiveModulePanels
Action.Application panels can also be added when using registerApp
, this can be done trough the AppOptions.
Extensions
Extensions are meant to extend or augment different, existing components in the EchopediaWeb core application or in EchoFramework - this gives the system flexibility. How the given extensions are handled is always up to the extendable component. EchoCore just provides the architecture for this.
Example:
There's a list of application links (both internal and external) in EchopediaWeb's Tag search results, in a TagItem
's header - with this we can open a tag in a different app.
This component was built to be extendable: it will connect EchoCore state and render additional links based on the registered extensions for this component in EchoCore.
Here's how an EchoModule can register it's own link into this particular component, with the help of a dedicated registerContextualAppLink()
function:
// Register a simple, icon button component.
// It will handle simple linking to your app with the given tag.
export function setup(api: EchoModuleApi): void {
api.registerPage('/myEchoModule', MyEchoModule);
api.registerContextualAppLink({
label: 'Open in Awesome Echo App',
iconName: 'rotate_3d' // accepts EDS icon names
});
}
// For more complex scenarios: Register a custom button component, which uses it's own logic to navigate to your app.
import { MyCustomLinkButton } from './MyCustomLinkButton';
export function setup(api: EchoModuleApi): void {
api.registerPage('/myEchoModule', MyEchoModule);
api.registerContextualAppLink({
component: MyCustomLinkButton
});
}
The above mentioned extensions are utilized by ContextualAppLinks
component, which can be found in EchoFramework. Check it out to learn more about it!
It is also possible to register a custom extension either by using registerApp
through AppOptions
or by using the setup
function, EchoModuleApi
:
export function setup(api: EchoModuleApi): void {
api.registerPage('/myEchoModule', MyEchoModule);
api.registerExtension({
key: 'unique-key',
extends: 'PredifenedComponentName',
component: MyCustomExtensionComponent,
options: { // custom options used by the extendable component
listOfUsers: [ 'Mary Sue', 'John Doe' ]
isApproved: false,
...
}
})
}
Each extendable component utilizes it's extensions in a unique way: if you want to use registerExtension()
function, make sure you're familiar with the required options
for the given, extendable component.
A component can have any number of extensions.
The useExtensions()
hook can help you with getting all the registered extensions for a given component.
Extensions are stored in the GlobalState.registry.extensions
;
Register Page / Route
Under the hood registerRoute
is what registerApp
, registerAppWithKey
, and registerPage
, uses. An app is just a route with a added component. registerPag
can be used to register pages which needs no direct connection.
import { EchoModuleApi, PanelType } from "@equinor/echo-core"
import { Icon, themeConst } from '@equinor/echo-components';
import TestModule from "./testModule";
import Panel from "./panel";
export function setup(api: EchoModuleApi): void {
const icon = (): JSX.Element => <Icon color={themeConst.equiGreen1} name='tag' />;
api.registerApp(TestModule, {
homeScreen: true,
panels: { component: Panel, key: 'test', icon, label: 'test', panelType: PanelType.left }
});
}
Manifest
As you can see in the code sample above, TestModule
module is registered as an app. The app will register a route and link this using the modules appManifest. here is an example of such manifest.
{
name: 'Test',
requireRef: '',
key: 'test',
shortName: 'test',
path: '/test',
fileUri: 'echo-test-module.js',
version: '0.0.1',
}
Global State
The global state is meant for application related data. Large data sets will slow down the performance of the application / client so keeping this to the minimum is key.
Lets take a look at at current global state.
interface GlobalState {
app: EchoAppState;
modules: Array<AppModule>;
coreComponents: EchoCoreComponents;
registry: RegistryState;
ui: Dict<UI>;
userProfile?: User;
userPhotoUrl?: string;
legendOptions: LegendOptions;
settings: Settings;
plantsData: PlantsData;
procosysProjectsData: ProcosysProjectsData;
moduleState: EchoCustomState<unknown>;
moduleContext: ModuleContext<unknown>;
}
There are three sections which are particularly interesting, the registry, moduleState
and moduleContext
. First let's look at module state and context.
Module State / Context
Echo core provides two ways of utilizing the module state. The first method using just the moduleState. the state can be initialized and retrieve with useAppModuleState
.
interface State {
data: string
name: string;
lastName: string
}
const state = {
data: "This is a module state",
name: "Tom",
lastName: "Jones"
}
// This will register the module state
useAppModuleState(state);
// To retrieve the module state with types one can write.
const { name, lastName } = useAppModuleState<State>();
There are several ways to update the module state.
const newState = {
data: "using the updateModuleState will replace the current state";
name: "Tom",
lastName: "Jones",
}
updateModuleState(newState);
// This will update just the name property of the module State.
updateSpecificModuleState("name", "John");
Module Context and state
The state can be use in conjunction with a context the context is wrapping the whole application this can be sean in EchoContent.tsx
file in EchoFramework
. The ModuleContextProvider
is provided form EchoCore, will always provide the current moduleState
. This will allow you to use regular react context.
Use to communicate with panels and header!
import { ModuleContextProvider } from '@equinor/echo-core';
import React from 'react';
import { LayoutProps } from '../components';
import CorePanelLeft from '../components/panel/corePanelLeft';
import CorePanelRight from '../components/panel/corePanelRight';
interface CorePanelsProps {
children: React.ReactNode;
Legend?: React.FC;
}
export const EchoContent: React.FC<CorePanelsProps> = ({ children, Legend }: CorePanelsProps): JSX.Element => {
return (
<ModuleContextProvider>
<CorePanelLeft />
<CorePanelRight />
{children}
{Legend && <Legend />}
</ModuleContextProvider>
);
};
A context can be registered by using the registerModuleContext
function provided by EchoCore.
RegistryState
This is the the Echo global app sub-state container for registering application components. This state is the most important state of the whole application and contains all routes, panels and appLinks.
As EchoCore' responsibility is mainly to provide developers the tools to register and retrieve information form the global state the usage of the RegistryState
can be found in EchoFramework's documentation which can be found here
Analytics
Logging to Application Insights.
Analytics general info in runbook here
Create the logging module with:
const analyticsLog = analytics.createAnalyticsModule("xld");
Optional static event or error properties can also be attached to all events or errors with:
const analyticsLog = analytics.createAnalyticsModule("xld", { staticEventProperties: {ver: 1.01}, staticErrorProperties: {ver: 1.01} });
Log event with:
analyticsLog.trackEvent(event: AnalyticsEvent)
analyticsLog.trackEventBy(objectName: string, actionName: string, properties: AnalyticsPropertyTypes)
analyticsLog.trackEventBy("Document", "Opened", {docNo: "abc123", fileId: 12345 })
The format in Application Insights will be:
- appId.Object.Action
- appId_subModule.Object.Action
- ep.Pdf.Opened
- ep_xld.Document.Opened
- ep_3d.Viewer.ClickedTag
Error logging should usually not be called directly. Instead use handleError (TODO - update this documentation when we have handleError in core), which will report the error to appInsight.
analyticsLog.logError(error)
Echo-inField should in addition do some configuration, like setting user, instCode and company, which will be supplied with each event.
analyticsConfiguration
Error handling
Error Reporting to application insight.
Register a helper function errorHandler
like this:
export const errorHandler = (exception: Error | BaseError): void => {
EchoCore.handleErrors(exception, myModuleAnalyticsModule);
};
BaseError
extends Error
, and is recommended to use instead of Error
- It makes sure that that the error is only logged once to appInsights
- All properties of
BaseError
are logged to AppInsights, even if they are private - It supports
innerError
, either as typeError
orRecord<string, unknown>
Use the toError
helper function for converting a caught unknown to a proper Error object.
It's recommended to extend BaseError
, and decorate it with extra properties, as seen in the example below.
Example of a customError and typical error flow.
export class PdfError extends BaseError {
docNo: string;
constructor(args: { message: string; docNo: string; innerError?: Error }) {
super({ name: 'PdfError', message: args.message, innerError: args.innerError });
this.docNo = args.docNo;
}
}
...
try {
loadPdf();
} catch(error) {
throw new PdfError({ message:"load pdf failed", docNo: "a-73", innerError: toError(error) });
}
}
Old repository of echoCore
Keeping it for historical reasons. https://github.com/equinor/EchoCore and before that, https://github.com/equinor/EchoFramework