Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ Not needed if you're using Keycloak.
#### `COMPONENT_REPOSITORY_UI_ENABLED` (boolean, default: `false`)
a boolean used to determine whether the Component Repository (former Digital Exchange) should be enabled or not.

#### `ENTANDO_FEATURE_FLAGS` (string, default: `""`)
a comma-separated list of feature flags to enable. Available flags:

- `HEADLESS_WIDGET_CONFIG` — enables legacy widget configuration via headless Struts iframe. When enabled, widgets with `configUi.customElement` set to `LEGACY_CONFIG` will render their configuration form inside an iframe pointing to the Struts admin console.

Example with a single flag:
```
ENTANDO_FEATURE_FLAGS=HEADLESS_WIDGET_CONFIG
```

Example with multiple flags:
```
ENTANDO_FEATURE_FLAGS=HEADLESS_WIDGET_CONFIG,ANOTHER_FLAG
```

#### `KEYCLOAK_ENABLED` (boolean, default: `false`)
a boolean that enables authentication through [Keycloak](https://www.keycloak.org/). Won't be used if `USE_MOCKS` is set to true.

Expand Down
1 change: 1 addition & 0 deletions config/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const REACT_APP = /^REACT_APP_/i;
const RUNTIME_OVERRIDABLE_VARS = [
'COMPONENT_REPOSITORY_UI_ENABLED',
'DOMAIN',
'ENTANDO_FEATURE_FLAGS',
'KEYCLOAK_JSON',
];

Expand Down
3 changes: 2 additions & 1 deletion docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ then
else
ENV_KEYCLOAK_JSON="${KEYCLOAK_JSON}"
fi
ENV_JSON='{"DOMAIN":"'"$ENV_DOMAIN"'", "KEYCLOAK_JSON":"'"$ENV_KEYCLOAK_JSON"'"}'
ENV_FEATURE_FLAGS=${ENTANDO_FEATURE_FLAGS:-""}
ENV_JSON='{"DOMAIN":"'"$ENV_DOMAIN"'", "KEYCLOAK_JSON":"'"$ENV_KEYCLOAK_JSON"'", "ENTANDO_FEATURE_FLAGS":"'"$ENV_FEATURE_FLAGS"'"}'
ESCAPED_ENV_JSON=$(echo $ENV_JSON | sed 's/\"/\\\"/g' | sed 's/\//\\\//g' | tr -d '\n' | tr -d '[[:blank:]]')

sed -i 's/"REACT_APP_ENV"/'"$ESCAPED_ENV_JSON"'/g' $HOME/app-builder/index.html
Expand Down
23 changes: 23 additions & 0 deletions src/helpers/featureFlags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const getProcessEnvVar = envVar => process.env[envVar] || '';

const getWindowEnvVar = envVar => (window && window.env && window.env[envVar] ? window.env[envVar] : '');

const getRawValue = (envVar) => {
if (process.env.NODE_ENV === 'development') {
return getProcessEnvVar(envVar);
}
return getWindowEnvVar(envVar) || getProcessEnvVar(envVar);
};

const FEATURE_FLAGS_KEY = 'ENTANDO_FEATURE_FLAGS';

export const getFeatureFlags = () => {
const raw = getRawValue(FEATURE_FLAGS_KEY);
if (!raw) return [];
return raw.split(',').map(f => f.trim()).filter(Boolean);
};

export const hasFeatureFlag = flag => getFeatureFlags().includes(flag);

// Known feature flags
export const FEATURE_FLAG_HEADLESS_WIDGET_CONFIG = 'HEADLESS_WIDGET_CONFIG';
5 changes: 5 additions & 0 deletions src/helpers/legacyWidget.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import getAppBuilderWidgetForm from 'helpers/getAppBuilderWidgetForm';
import { adminConsoleUrl } from 'helpers/urlUtils';
import { hasFeatureFlag, FEATURE_FLAG_HEADLESS_WIDGET_CONFIG } from 'helpers/featureFlags';

export const LEGACY_CONFIG_CUSTOM_ELEMENT = 'LEGACY_CONFIG';

// A widget is "legacy" when its configUi.customElement is set to LEGACY_CONFIG
// and it has no internal app-builder form registered in getAppBuilderWidgetForm.
// Gated behind the HEADLESS_WIDGET_CONFIG feature flag.
export const isLegacyWidget = (widget) => {
if (!hasFeatureFlag(FEATURE_FLAG_HEADLESS_WIDGET_CONFIG)) {
return false;
}
if (!widget || !widget.configUi || widget.configUi.customElement !== 'LEGACY_CONFIG') {
return false;
}
Expand Down
75 changes: 75 additions & 0 deletions test/helpers/featureFlags.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { getFeatureFlags, hasFeatureFlag, FEATURE_FLAG_HEADLESS_WIDGET_CONFIG } from 'helpers/featureFlags';

describe('helpers/featureFlags', () => {
const originalEnv = process.env;

beforeEach(() => {
process.env = { ...originalEnv, NODE_ENV: 'development' };
});

afterEach(() => {
process.env = originalEnv;
});

describe('getFeatureFlags', () => {
it('returns empty array when env var is not set', () => {
delete process.env.ENTANDO_FEATURE_FLAGS;
expect(getFeatureFlags()).toEqual([]);
});

it('returns empty array when env var is empty', () => {
process.env.ENTANDO_FEATURE_FLAGS = '';
expect(getFeatureFlags()).toEqual([]);
});

it('parses a single flag', () => {
process.env.ENTANDO_FEATURE_FLAGS = 'HEADLESS_WIDGET_CONFIG';
expect(getFeatureFlags()).toEqual(['HEADLESS_WIDGET_CONFIG']);
});

it('parses multiple comma-separated flags', () => {
process.env.ENTANDO_FEATURE_FLAGS = 'HEADLESS_WIDGET_CONFIG,OTHER_FLAG,THIRD';
expect(getFeatureFlags()).toEqual(['HEADLESS_WIDGET_CONFIG', 'OTHER_FLAG', 'THIRD']);
});

it('trims whitespace around flags', () => {
process.env.ENTANDO_FEATURE_FLAGS = ' HEADLESS_WIDGET_CONFIG , OTHER_FLAG ';
expect(getFeatureFlags()).toEqual(['HEADLESS_WIDGET_CONFIG', 'OTHER_FLAG']);
});

it('ignores empty segments from trailing commas', () => {
process.env.ENTANDO_FEATURE_FLAGS = 'FLAG_A,,FLAG_B,';
expect(getFeatureFlags()).toEqual(['FLAG_A', 'FLAG_B']);
});
});

describe('hasFeatureFlag', () => {
it('returns true when the flag is present', () => {
process.env.ENTANDO_FEATURE_FLAGS = 'HEADLESS_WIDGET_CONFIG';
expect(hasFeatureFlag(FEATURE_FLAG_HEADLESS_WIDGET_CONFIG)).toBe(true);
});

it('returns false when the flag is not present', () => {
process.env.ENTANDO_FEATURE_FLAGS = 'OTHER_FLAG';
expect(hasFeatureFlag(FEATURE_FLAG_HEADLESS_WIDGET_CONFIG)).toBe(false);
});

it('returns false when no flags are set', () => {
delete process.env.ENTANDO_FEATURE_FLAGS;
expect(hasFeatureFlag(FEATURE_FLAG_HEADLESS_WIDGET_CONFIG)).toBe(false);
});

it('works with multiple flags', () => {
process.env.ENTANDO_FEATURE_FLAGS = 'FOO,HEADLESS_WIDGET_CONFIG,BAR';
expect(hasFeatureFlag(FEATURE_FLAG_HEADLESS_WIDGET_CONFIG)).toBe(true);
expect(hasFeatureFlag('FOO')).toBe(true);
expect(hasFeatureFlag('UNKNOWN')).toBe(false);
});
});

describe('FEATURE_FLAG_HEADLESS_WIDGET_CONFIG', () => {
it('has the expected value', () => {
expect(FEATURE_FLAG_HEADLESS_WIDGET_CONFIG).toBe('HEADLESS_WIDGET_CONFIG');
});
});
});
Loading