Skip to content

Instantly share code, notes, and snippets.

@NoriSte
Last active December 5, 2022 08:40
Show Gist options
  • Save NoriSte/f85fb95befa3cf1e26b6ea2e22375979 to your computer and use it in GitHub Desktop.
Save NoriSte/f85fb95befa3cf1e26b6ea2e22375979 to your computer and use it in GitHub Desktop.
OpenTelemetry Toggle logics with a React hook and with XState (they can be not totally aligned)
// ---------------------------------------------------------------
// CONTEXT
export type Context = {
// The current status of OpenTelemetry
status: 'enabled' | 'disabled';
// Whether the current metadata contains the configuration or not
metadataContainsConfig: boolean;
// Whether the current form state contains valid configuration or not
configFormIsValid: boolean;
// External callbacks
onEnable: () => Promise<void>;
onDisable: () => Promise<void>;
};
// If the feature is already enabled, it means that metadata contains a valid configuration. It's
// not possible the metadata contains an invalid configuration since the server prevents it.
export type EnabledContext = {
status: 'enabled';
metadataContainsConfig: true;
};
// When Disabled, the metadata can contain a configuration or not yet.
export type DisabledContext = {
status: 'disabled';
};
// When the metadata does not contain a configuration, the users need to fill up the form before
// enabling OpenTelemetry. This can happen only the first time the feature is enabled because then
// the configuration cannot be removed/reset.
export type PleaseFillTheFormContext = {
status: 'disabled';
configFormIsValid: false;
metadataContainsConfig: false;
};
// When the metadata does not contain a configuration but fhe form contains is, the users need to
// submit the form before enabling OpenTelemetry. This can happen only the first time the feature is
// enabled because then the configuration cannot be removed/reset.
export type PleaseSubmitTheFormContext = {
status: 'disabled';
configFormIsValid: true;
metadataContainsConfig: false;
};
type CanBeEnabled =
| { configFormIsValid: true }
| { metadataContainsConfig: true };
export type EnablingContext = { status: 'enabled' } & CanBeEnabled;
export type DisablingContext = { status: 'enabled' } & CanBeEnabled;
export const initialContext: Context = {
status: 'disabled',
metadataContainsConfig: false,
configFormIsValid: false,
onEnable: () => {
throw new Error('onEnable is not defined yet');
},
onDisable: () => {
throw new Error('onDisable is not defined yet');
},
};
// ---------------------------------------------------------------
// EVENTS
export type Events = InternalEvents | UserEvents;
// INTERNAL EVENTS
/**
* Internal events, helpful for typing purposes.
*/
type InternalEvents = SuccessEvent | FailureEvent;
export type SuccessEvent = { type: 'SUCCESS' };
export type FailureEvent = { type: 'FAILURE' };
// USER EVENTS
/**
* All the events triggered mostly by user inputs.
*/
type UserEvents = ConfigFormSubmittedEvent | ToggleEvent;
export type ConfigFormSubmittedEvent = { type: 'CONFIG_FORM_SUBMITTED' };
export type ToggleEvent = { type: 'TOGGLE' };
// ---------------------------------------------------------------
// STATES
/**
* All the possible states of the machine.
*/
export type States =
// INITIAL STATE
| { value: 'idle'; context: Context }
// INITIAL STATES
| { value: 'enabled'; context: Context & EnabledContext }
| { value: 'disabled'; context: Context & DisabledContext }
// ERROR STATES
| {
value: 'pleaseFillTheForm';
context: Context & PleaseFillTheFormContext;
}
| {
value: 'pleaseSubmitTheForm';
context: Context & PleaseSubmitTheFormContext;
}
// LOADING STATES
| { value: 'enabling'; context: Context & EnablingContext }
| { value: 'disabling'; context: Context & DisablingContext };
export const toggleOpenTelemetryMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QBcD2UoBswHkAOYAdgCpjYC2YyATgJ4B0AlhNgMQDaADALqKh6pYjZI1SE+IAB6IATAHYZ9AIwBOAKwBmJRoAcCnQDYdnACwAaELUQ6N9TvfsaVMzis6qZAX08W0GbPhEpBRUdEwsYBxKvEggAkIiYhLSCPKKqpraejKGxuaWiE4qdnJKJgZqnAYGChoG3r7oWLgEJGRglDQMEIywAIYARtgQrMQ4AOLjADIAolwx-ILCouKxKQYyxaZGKrpuBiY2Flapakr0KpeX8pw6Oi5GDSB+zYFtIV30Pf1DkKMT0zm0Qk8WWSTWiA2W3KOl2sKqhw0x0QJjUJjsDhMpjkmhUOKeLwCrWCHVC3V6g2G-0ms3YMgWcSWiVWoHWmzsMLh+0RyIQGk0GMcWiUShxMjUBKaRKC7U6YSIlMYhCgrAAygBVADCmpmqtV8xBTJWyUQSnFBnoBx0JnKOTRnHFvI0Tnozo0WLqphkBnckv8LRlH3lhEVytYADEAIIASSm6oASnMeIaEsaIQgVEo1K77nIVIYtFCZE6HfRKvYzZmHYY1PUfM8pQH3qTPgrfiMxjSkwzQcyTQgzdULldVKK0TIzby1HJ0Tj7JVpwZtKY-a9ibKyV8KUMlSqNdrdfrk7Fe2nWbIFMp1FpdPojKZeTbs5xSjCtPdOBovPXCU2SXLyR+TBdwjGM40TA0TyNcFz1SS8MhvbJcgfAoEAMdQywcXZSiXSpv0af03n-Tc8GwPpYDAcNGEwTBiAAC0o1BqHIVhNRwAA5cNo3GAB9cMcHjABZHiNQAIUE6NiGIGYABFIMWVMYKkRA5FKS1RSUD8dG0ExtF5FxznLec0WqXTqlXaVmwA+hSLAciwFVABXAZyGEejGOY1hBJmYhIxkyNfJ4tjOO4vifM1AAJWT5MZRSWWUhAcVsWtxUcLEXGdR9P0w+dnW0CcdAlJ5CFQCA4AkX8iI3LoUzBeKUgAWgMXkGuzK4rlcExdiMTSLL-aqwmYbBar7dNRVsdDNBkEwtHzdRi1QvYLlMbT1HcYw8xMPqqqDQDKUgEazwSmFlA05wtGmtQ1EfDQ5FdCcTBxXIcR9ORtvXXb6DbYDlUOpSUiUT9ik0A5+RmtQVHKOQnSzMsbCcDQHRB613sDFtg32iA-vq00sXOTYjDUeQym2aHUNrYGHDUGxtLkdwtp-RsdvRvad1+qC4v7EwFpOK16DnIGnqzXZUaskiyIoqiaPc8MmPIbH+0Bl0sw2ac6czVR9NRC4lGqapnB9dLReIz5bPspyXLchjZeYhWxqB5QUrV1wRRUR9Hp1moynQ71RW8bwgA */
createMachine(
{
context: initialContext,
tsTypes: {} as import('./toggleStateMachine.typegen').Typegen0,
schema: { context: {} as Context, events: {} as Events },
id: 'toggleOpenTelemetry',
initial: 'idle',
states: {
idle: {
always: [
{
target: 'enabled',
cond: 'isEnabled',
},
{
target: 'disabled',
cond: 'isDisabled',
},
],
},
disabled: {
on: {
TOGGLE: [
{
target: 'pleaseFillTheForm',
cond: 'isEnablingWithoutConfiguration',
},
{
target: 'pleaseSubmitTheForm',
cond: 'isEnablingWithoutSubmittingForm',
},
{
target: 'enabling',
},
],
},
},
enabling: {
invoke: {
src: 'callOnEnable',
},
on: {
SUCCESS: {
target: 'enabled',
},
FAILURE: {
target: 'disabled',
},
},
},
enabled: {
on: {
TOGGLE: {
target: 'disabling',
},
},
},
disabling: {
invoke: {
src: 'callOnDisable',
},
on: {
SUCCESS: {
target: 'disabled',
},
FAILURE: {
target: 'enabled',
},
},
},
pleaseFillTheForm: {
on: {
CONFIG_FORM_SUBMITTED: {
target: 'disabled',
},
},
},
pleaseSubmitTheForm: {
on: {
CONFIG_FORM_SUBMITTED: {
target: 'disabled',
},
},
},
},
},
{
services: {
callOnDisable: context => send => {
context
.onDisable()
.then(() => send({ type: 'SUCCESS' }))
.catch(() => send({ type: 'FAILURE' }));
},
callOnEnable: context => send => {
context
.onEnable()
.then(() => send({ type: 'SUCCESS' }))
.catch(() => send({ type: 'FAILURE' }));
},
},
guards: {
isEnabled: context => context.status === 'enabled',
isDisabled: context => context.status === 'disabled',
isEnablingWithoutConfiguration: context =>
context.metadataContainsConfig === false &&
context.configFormIsValid === false,
isEnablingWithoutSubmittingForm: context =>
context.metadataContainsConfig === false &&
context.configFormIsValid === true,
},
}
);
import type { ChangeEvent } from 'react';
import { useRef, useState, useEffect } from 'react';
import { useIsUnmounted } from '../useIsUnmounted';
interface Params {
status: 'enabled' | 'disabled';
onEnable: () => Promise<void>;
onDisable: () => Promise<void>;
// External conditions
formIsValid: boolean;
metadataContainsConfig: boolean;
}
export function useToggleState(params: Params) {
const { status, onEnable, onDisable, metadataContainsConfig, formIsValid } =
params;
const [checked, setChecked] = useState(status === 'enabled');
const [submitting, setSubmitting] = useState<boolean>(false);
const [error, setError] = useState<
'noErrors' | 'setupConfigFirst' | 'submitConfigFormFirst'
>('noErrors');
const isUnmounted = useIsUnmounted();
// DISABLE OPENTELEMETRY
const disableOpenTelemetry = async () => {
// The toggle is controlled, hence we need to immediately updates the UI to reflect the users' action
setChecked(false);
setSubmitting(true);
try {
await onDisable();
} catch {
if (isUnmounted()) return;
// In case of server errors while disabling OpenTelemetry, we need to revert the UI to the previous state
setChecked(true);
// Server errors are presented the users through notifications, no need to se them here
}
if (isUnmounted()) return;
setSubmitting(false);
};
// ENABLE OPENTELEMETRY
const enableOpenTelemetry = async () => {
// The toggle is controlled, hence we need to immediately updates the UI to reflect the users' action
setChecked(true);
setSubmitting(true);
try {
await onEnable();
} catch {
if (isUnmounted()) return;
// In case of server errors while disabling OpenTelemetry, we need to revert the UI to the previous state
setChecked(false);
// Server errors are presented the users through notifications, no need to se them here
}
if (isUnmounted()) return;
setSubmitting(false);
};
// REACT TO TOGGLE CHANGES
function onChange(event: ChangeEvent<HTMLInputElement>) {
const isDisablingOpenTelemetry = event.target.checked === false;
// Disabling OpenTelemetry is always allowed because it's harmless (and that also means that
// previously it was enabled, hence the configuration was valid and already stored in the metadata)
if (isDisablingOpenTelemetry) {
disableOpenTelemetry();
return;
}
if (!formIsValid && !metadataContainsConfig) {
// If the metadata does not contain a valid configuration (so it'f a first-time setup) and the
// config form is not valid, it means that the users have not filled the form yet and they must do
// it before enabling OpenTelemetry
setError('setupConfigFirst');
setChecked(false);
return;
}
if (formIsValid && !metadataContainsConfig) {
// If the metadata does not contain a valid configuration (so it'f a first-time setup) and the
// config form is valid, it means that the users have already filled the form and they must submit
// it before enabling OpenTelemetry. Once submitted, the metadata will change
setError('submitConfigFormFirst');
setChecked(false);
return;
}
// At this point, the metadata already contains a valid configuration, then we can enable OpenTelemetry.
// Please note that this conditions have been written to not cause problems in case of unmanaged
// edge cases. In fact, if something has not been managed correctly in the previous steps, we
// allow enabling open telemetry anyway!
enableOpenTelemetry();
}
const externalConditionsRef = useRef({ metadataContainsConfig, formIsValid });
// ERRORS RESETTING
useEffect(() => {
const {
metadataContainsConfig: prevMetadataContainsConfig,
formIsValid: prevFormIsValid,
} = externalConditionsRef.current;
// The opposite situation opposite should be impossible since at te moment the OT config cannot
// be removed/reset
if (
prevMetadataContainsConfig === false &&
metadataContainsConfig === true
) {
setError('noErrors');
}
// For every changes happening in the form, we must reset the form-related error
if (prevFormIsValid !== formIsValid) {
setError('noErrors');
}
// Update the ref after storing the previous values
externalConditionsRef.current = { metadataContainsConfig, formIsValid };
}, [error, metadataContainsConfig, formIsValid]);
return { onChange, checked, submitting, error };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment