Skip to content

Commit

Permalink
improvement: allow to enable/disable content based on admin version (#…
Browse files Browse the repository at this point in the history
…353)

* improvement: allow to enable/disable content based on admin version
* docs: add info about FeatureFlags and LocalStorage usage

Signed-off-by: Nastya Rusina <[email protected]>
  • Loading branch information
anrusina authored Apr 6, 2022
1 parent 53eb565 commit 05592e3
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 181 deletions.
142 changes: 62 additions & 80 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Flyte Console Contribution Guide

First off, thank you for thinking about contributing!
First off all, thank you for thinking about contributing!
Below you’ll find instructions that will hopefully guide you through how to contribute to, fix, and improve Flyte Console.

## Protobuf and Debug Output
Expand All @@ -19,7 +19,7 @@ output must be enabled manually. You can do this by setting a flag in
localStorage using the console: `localStorage.debug = 'flyte:*'`. Each module in
the application sets its own namespace. So if you'd like to only view output for
a single module, you can specify that one specifically
(ex. ``localStorage.debug = 'flyte:adminEntity'`` to only see decoded Flyte
(ex. `localStorage.debug = 'flyte:adminEntity'` to only see decoded Flyte
Admin API requests).

## Storybook
Expand All @@ -33,35 +33,18 @@ You can run storybook with `yarn run storybook`, and view the stories at http://
## Feature flags

We are using our internal feature flag solution to allow continuos integration,
while features are in development.
while features are in development. There are two types of flags:

- **FeatureFlag**: boolean flags which indicate if feature is enabled.
- **AdminFlag**: the minimal version of flyteadmin in which feature supported.

All flags currently available could be found in [/FeatureFlags/defaultConfig.ts](./src/basics/FeatureFlags/defaultConfig.ts)
file. Most of them under active development, which means we don't guarantee it will work as you expect.

If you want to add your own flag, you need to add it to both `enum FeatureFlag` and `defaultFlagConfig`
under production section.
Initally all flags must be disabled, meaning you code path should not be executed by default.

**Example - adding flags:**

```javascript
enum FeatureFlags {
...
AddNewPage: 'add-new-page'
UseCommonPath: 'use-common-path'
}

export const defaultFlagConfig: FeatureFlagConfig = {
...
'add-new-page': false, // default/prior behavior doesn't include new page
'use-common-path': true, // default/prior behavior uses common path
};
```

To use flags in code you need to ensure that the most top level component is wrapped by `FeatureFlagsProvider`.
By default we are wrapping top component in Apps file, so if you do not plan to include
feature flags checks in the `\*.tests.tsx` - you should be good to go.
To check flag's value use `useFeatureFlag` hook.

**Example - flag usage**:

```javascript
Expand All @@ -70,87 +53,87 @@ import { FeatureFlag, useFeatureFlag } from 'basics/FeatureFlags';
export function MyComponent(props: Props): React.ReactNode {
...
const isFlagEnabled = useFeatureFlag(FeatureFlag.AddNewPage);

return isFlagEnabled ? <NewPage ...props/> : null;
}
```

During your local development you can either:
* temporarily switch flags value in runtimeConfig as:
```javascript
let runtimeConfig = {
...defaultFlagConfig,
'add-new-page': true,
};
```
* turn flag on/off from the devTools console in Chrome
![SetFeatureFlagFromConsole](https://user-images.githubusercontent.com/55718143/150002962-f12bbe57-f221-4bbd-85e3-717aa0221e89.gif)
More info in [FEATURE_FLAGS.md](src/basics/FeatureFlags/FEATURE_FLAGS.md)

## Local storage

#### Unit tests
We allow to save user "settings" choice to the browser Local Storage, to persist specific values between sessions. The local storage entry is stored as a JSON string, so can represent any object. However, it is a good practise to minimize your object fields prior to storing.

If you plan to test non-default flag value in your unit tests, make sure to wrap your component with `FeatureFlagsProvider`.
Use `window.setFeatureFlag(flag, newValue)` function to set needed value and `window.clearRuntimeConfig()`
to return to defaults. Beware to comment out/remove any changes in `runtimeConfig` during testing;
All available LocalCacheItems could be found in [/LocalCache/defaultConfig.ts](./src/basics/LocalCache/defaultConfig.ts). We are using `flyte.` prefix in items which are storing user settings.

**Example - flag usage**:

```javascript
function TestWrapper() {
return <FeatureFlagsProvider> <TestContent /> </FeatureFlagsProvider>
import { LocalCacheItem, useLocalCache } from 'basics/LocalCache';

export function MyComponent(props: Props): React.ReactNode {
...
const [showTable, setShowTable] = useLocalCache(LocalCacheItem.ShowWorkflowVersions);

return showTable ? <SomeComponent ...props onClick={() => setShowTable(!showTable)}/> : null;
}
```

## Unit tests

You can run unit tests locally, for both of the command listed below `NODE_ENV=test` is set-up, so if you need a specific error/log/mock treatment for these cases feel free to use isTestEnv() check

describe('FeatureFlags', () => {
afterEach(() => {
window.clearRuntimeConfig(); // clean up flags
});

it('Test', async () => {
render(<TestWrapper />);

window.setFeatureFlag(FeatureFlag.FlagInQuestion, true);
await waitFor(() => {
// check after flag changed value
});
});
```
import { isTestEnv } from 'common/env';
...
if (isTestEnv()) {...}
```

To run unit tests locally: `yarn test`
To check coverage `yarn test-coverage`

## Google Analytics

This application makes use of the `react-ga4 <https://github.com/PriceRunner/react-ga4>`_
libary to include Google Analytics tracking code in a website or app. For all the environments, it is configured using ENABLE_GA environment variable.
By default, it's enabled like this: ``ENABLE_GA=true``. If you want to disable it, just set it false. (ex. ``ENABLE_GA=false``).
This application makes use of the `react-ga4 <https://github.com/PriceRunner/react-ga4>` libary to include Google Analytics tracking code in a website or app. For all the environments, it is configured using ENABLE_GA environment variable.
By default, it's enabled like this: `ENABLE_GA=true`. If you want to disable it, just set it false. (ex. `ENABLE_GA=false`).

## 📦 Install Dependencies

Running flyteconsole locally requires [NodeJS](https://nodejs.org) and
[yarn](https://yarnpkg.com). We recommend for you to use **asdf** to manage NodeJS version.
You can find currently used versions in `.tool-versions` file.

* Install asdf through homebrew
``` bash
- Install asdf through homebrew

```bash
brew install asdf
```

* (Optional) If you are using **M1 MacBook**, you will need to install `vips` for proper build experience
``` bash
- (Optional) If you are using **M1 MacBook**, you will need to install `vips` for proper build experience

```bash
brew install vips
```

* Add Yarn plugin to asdf, to manage yarn versions
``` bash
- Add Yarn plugin to asdf, to manage yarn versions

```bash
asdf plugin-add yarn https://github.com/twuni/asdf-yarn.git
brew install gpg
```

* From flyteconsole directory - install proper NodeJS and yarn versions:
``` bash
- From flyteconsole directory - install proper NodeJS and yarn versions:

```bash
asdf install
```

* Install nodepackages
``` bash
- Install nodepackages

```bash
yarn install
```

## CORS Proxying: Recommended Setup

In the common hosting arrangement, all API requests will be to the same origin
Expand All @@ -160,56 +143,55 @@ domain you will need to configure your enviornment to support CORS. One example
hosting the Admin API on a different domain than the console. Another example is
when fetching execution data from external storage such as S3.

The fastest (recommended) way to setup a CORS solution is to do so within the browser.
The fastest (recommended) way to setup a CORS solution is to do so within the browser.
If you would like to handle this at the Node level you will need to disable authentication
(see below).

*NOTE:* Do not configure for both browser and Node solutions.
_NOTE:_ Do not configure for both browser and Node solutions.

These instructions require using Google Chrome. You will also need to identify the
These instructions require using Google Chrome. You will also need to identify the
URL of your target FlyteAdmin API instance. These instructions will use
`https://different.admin.service.com` as an example.

1. Set `ADMIN_API_URL` and `ADMIN_API_USE_SSL`

```
export ADMIN_API_URL=https://different.admin.service.com
export ADMIN_API_USE_SSL="https"
```
*NOTE:* Add these to your local profile (e.g., `./profile`) to prevent having to do this step each time
_NOTE:_ Add these to your local profile (e.g., `./profile`) to prevent having to do this step each time
2. Generate SSL certificate
Run the following command from your `flyteconsole` directory
Run the following command from your `flyteconsole` directory
```bash
make generate_ssl
```
3. Add new record to hosts file
```bash
```bash
sudo vim /etc/hosts
```
Add the following record
Add the following record
```
127.0.0.1 localhost.different.admin.service.com
```
4. Install Chrome plugin: [Allow CORS: Access-Control-Allow-Origin](https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf)
>*NOTE:* Activate plugin (toggle to "on")
> _NOTE:_ Activate plugin (toggle to "on")
5. Start `flyteconsole`
```bash
yarn start
```
Your new localhost is [localhost.different.admin.service.com](http://localhost.different.admin.service.com)
Your new localhost is [localhost.different.admin.service.com](http://localhost.different.admin.service.com)
> Ensure you don't have `ADMIN_API_URL` or `DISABLE_AUTH` set (e.g., in your `/.profile`.)
28 changes: 28 additions & 0 deletions src/basics/FeatureFlags/AdminFlag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useAdminVersion } from 'components/hooks/useVersion';
import { AdminFlag, AdminVersion, baseAdminConfig } from './defaultConfig';

export const useIsEnabledInAdmin = (flag: AdminFlag): boolean => {
const { adminVersion } = useAdminVersion();
if (!adminVersion || adminVersion === '') {
return false;
}

// Split version to two array items - [Major][Minor.Patch]
const versionSplit = adminVersion.replace(/\./, '&').split('&');
const curVersion: AdminVersion = {
major: parseInt(versionSplit[0], 10) ?? 0,
minor: parseFloat(versionSplit[1]) ?? 0.1,
};

const requieredVersion = baseAdminConfig[flag] ?? null;
// required version is less or equal current version - return true.
if (
requieredVersion &&
(requieredVersion.major < curVersion.major ||
(requieredVersion.major === curVersion.major && requieredVersion.minor <= curVersion.minor))
) {
return true;
}

return false;
};
81 changes: 81 additions & 0 deletions src/basics/FeatureFlags/FEATURE_FLAGS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
## Feature Flags

We are using our internal feature flag solution to allow continuos integration, while features are in development. There are two types of flags:

FeatureFlag: boolean flags which indicate if feature is enabled.
AdminFlag: the minimal version of flyteadmin in which feature supported.
All flags currently available could be found in /FeatureFlags/defaultConfig.ts file. Most of them under active development, which means we don't guarantee it will work as you expect.

If you want to add your own flag, you need to add it to both enum FeatureFlag and defaultFlagConfig under production section. Initally all flags must be disabled, meaning you code path should not be executed by default

**Example - adding flags:**

```javascript
enum FeatureFlags {
...
AddNewPage: 'add-new-page'
UseCommonPath: 'use-common-path'
}

export const defaultFlagConfig: FeatureFlagConfig = {
...
'add-new-page': false, // default/prior behavior doesn't include new page
'use-common-path': true, // default/prior behavior uses common path
};
```

To use flags in code you need to ensure that the most top level component is wrapped by `FeatureFlagsProvider`.
By default we are wrapping top component in Apps file, so if you do not plan to include
feature flags checks in the `\*.tests.tsx` - you should be good to go.
To check flag's value use `useFeatureFlag` hook.

**Example - flag usage**:

```javascript
import { FeatureFlag, useFeatureFlag } from 'basics/FeatureFlags';

export function MyComponent(props: Props): React.ReactNode {
...
const isFlagEnabled = useFeatureFlag(FeatureFlag.AddNewPage);

return isFlagEnabled ? <NewPage ...props/> : null;
}
```

During your local development you can either:

- temporarily switch flags value in runtimeConfig as:
```javascript
let runtimeConfig = {
...defaultFlagConfig,
'add-new-page': true,
};
```
- turn flag on/off from the devTools console in Chrome
![SetFeatureFlagFromConsole](https://user-images.githubusercontent.com/55718143/150002962-f12bbe57-f221-4bbd-85e3-717aa0221e89.gif)

#### Unit tests

If you plan to test non-default flag value in your unit tests, make sure to wrap your component with `FeatureFlagsProvider`.
Use `window.setFeatureFlag(flag, newValue)` function to set needed value and `window.clearRuntimeConfig()`
to return to defaults. Beware to comment out/remove any changes in `runtimeConfig` during testing;

```javascript
function TestWrapper() {
return <FeatureFlagsProvider> <TestContent /> </FeatureFlagsProvider>
}

describe('FeatureFlags', () => {
afterEach(() => {
window.clearRuntimeConfig(); // clean up flags
});

it('Test', async () => {
render(<TestWrapper />);

window.setFeatureFlag(FeatureFlag.FlagInQuestion, true);
await waitFor(() => {
// check after flag changed value
});
});
```
Loading

0 comments on commit 05592e3

Please sign in to comment.