/ngx-feature-flag

:sparkles: Feature flag angular library :checkered_flag:

Primary LanguageTypeScriptApache License 2.0Apache-2.0

Build Status semantic-release

ngx-feature-flag

Purpose

The main motivation for doing feature toggling is to decouple the process for deploying code to production and releasing new features. This helps reducing risk, and allow us to easily manage which features to enable

Feature toggles decouple deployment of code from release of new features. For more insight, read Martin Fowler's and Pete Hodgson's Feature flag, A/B testing article

The fabric8 feature toggles is based on unleash upstream open source project.

Admin console

Admin consoles are available here:

NOTE: atm, we don't have any sync mechanism between the 2 instances. This is 2 separate DB.

How to login?

To login to toggles admin console, we use GitHub authentication and we provide some authorisation checking that your user is part of some organisation.

  • You will be prompted to authenticate though GH.
  • If not yet part of the RHDT organization, please open a pull request to the UserDB (under VPN; Check DevGuide for details)
  • If you're already part of the RHDT organization but not a member of either prod-preview or prod team, contact some admins in mattermost #fabric8-platform and ask them to add you. You will need to be an internal user to be granted access. Once granted, try again.

How to logout?

Search for Sign out in the admin console. You will be logged out of fabric8-toggles admin ui but not logged out of GH. If you want to login with a different GH account, logout of GH.

Getting started

  • You can import the lib in your application:

npm install ngx-feature-flag

Build

  • Pre-requisites
    • node v8.9.1+ (required by anuglar-cli 6+)
    • npm 5.5.1

This angular library is built using angular-cli's workspace. The main application is the demo app. The library source is under /project/ngx-feature-flag

  • Install the dependencies
npm i
  • Build library & demo app
npm run build
  • Run the library tests
npm test

NOTE: to run the library test in watch mode: npm run test:lib:dev

  • Fix lint errors (package import for ex)
npm run lint:fix

Run the demo

npm i
npm run build
npm start

Open your browser on http://localhost:4200/

Usage

FeatureTogglesService

Use the service directly to request the Feature.

use case 1: My feature is a module accessible by routing

Take the new launchpad wizard as an example:

  • In admin ui, add your new feature with a featureName (here AppLauncher), associate a enableByLevel strategy, enter the level parameter (internal, experimental, beta, released).
  • routing file: add FeatureFlagResolver(responsible to query the fabric8-toggles-service, also specify the name of your feature in featureName. Here AppLauncher should exactly match the feature name (ie: this is the external key between fabric8-ui and fabric8-toggles) defined in fabric8-toggles admin UI.
  {
    path: '_applauncher',
    resolve: {
      featureFlagConfig: FeatureFlagResolver
    },
    loadChildren: './app-launcher/app-launcher.module#AppLauncherModule',
    data: {
      title: 'App Launcher',
      featureName: 'AppLauncher'
    }
  },

use case 2: My feature is accessible on to a given set of user emails

Atm we have 2 rollout strategies to use with fabric8-toggles-service:

  • Enable by level enableByLevel is the most commonly used. It allows to tag a feature for a particular level. Feature flag level are: internal, experimental, beta, released.
    • internal will only show it to accounts with an @redhat.com email. It will have a notification on the screen: "This feature is open to Red Hat users only. You can manage pre-production features on your profile page."
    • experimental will be "This feature is experimental. You can manage pre-production features on your profile page."
    • beta notification will be "This feature is in beta. You can manage pre-production features on your profile page."
    • released makes the feature default for everyone.

NOTE: Anonymous user can only see the released state. For other level, you need to be logged-in.

  • Enable by emails. enableByEmails is used for a work in progress feature. You can create a feature with the strategy emableByEmails and then list the different users' emails of the other developers you want to collaborate with on this WIP feature.

For example, while working on new dashboard feature, Adam wanted to share the new dashboard only with a group of UI developers. Easy way is to used the enableByEmails. Once the feature is ready, Adam could remove the strategy enableByEmails and replace it with a strategy enableByLevel with a level of internal to start with.

Compoment

ngx-feature-flag provides 2 components to help you hide and show your features under feature-flag.

TIP: As a best practise, if you know a page of your app will have several feature flag, group them per page. We use a naming convention: Page.MyFeature where Page is the name you use in the FeatureFlagResolver.

use case 1: My feature is a component I want to hide/show

For example, let's hide EnvironmentWidgetComponent which is shown in a Environment page. Follow the best practices and name your feature Environment.Test. If your don't want to "group" features, you can call it simply Test.

  • In unleash admin ui, add your new feature with a featureName (here Environment.Test), associate a enableByLevel strategy, enter the level parameter (internal, experimental, beta, released). If this level (for ex: experimental) is below your user-consent level (for ex: beta), you won't see the component.
  • In your routing file: add FeatureFlagResolver(responsible to query the fabric8-toggles-service, also specify the name of your feature in featureName. Here Environment.Test should exactly match the feature name (ie: this is the external key between fabric8-ui and fabric8-toggles) defined in fabric8-toggles admin UI. Note: this step is optional if you do not group your component per page.
  • In your page or component template:
<f8-feature-toggle featureName="Environment.Test" [userLevel]="user"></f8-feature-toggle>
<ng-template #user>
  YOUR NEW HTML
</ng-template>
  • In your Module, import FeatureFlagModule:
@NgModule({
  imports: [CommonModule, FormsModule, RouterModule, MomentModule, FeatureFlagModule ],
  declarations: [EnvironmentWidgetComponent],
  exports: [EnvironmentWidgetComponent]
})
export class EnvironmentWidgetModule { }

NOTE: the component is hidden but still initialized.

use case 2: My feature is a refactored component I want to hide/show

Similar to precedent section. You put the new code in the HTML element that content user-level and the old code in default-level.

For example:

<f8-feature-toggle featureName="Analyze.newSpaceDashboard" [userLevel]="user" [defaultLevel]="default"></f8-feature-toggle>
<ng-template #user>
  YOUR NEW HTML
</ng-template>
<ng-template #default>
  YOUR OLD HTML
</ng-template>

use case 3: My feature is a component I want to dynamically load

This use case could be while the feature is under development and you want to make sure loading the component will not break the whole UI.

For example, let's dynamically load EnvironmentWidgetComponent to carry-on with the same example. but now we don't want to just hide the component, we want to make sure the component code is not loaded at all (this could be useful if for some reasons the component code may cause runtime failure).

  • In unleash admin ui, add your new feature with a featureName (here Environment.Test), associate a enableByLevel strategy, enter the level parameter (internal, experimental, beta, released). If this level (for ex: experimental) is below your user-consent level (for ex: beta), you won't see the component.
  • In analyze-overview.component.html template, replace <fabric8-environment-widget /> by <f8-feature-toggle-loader featureName="Environment.Test"></f8-feature-toggle-loader> where Environment.Test is the name of the feature. Choose an meaningful name like: env.widget. For test purpose here we reuse Environment.Test.
  • In the module associated to your dynamically loaded component add an entryComponents. For ex, in analyze-overview.module.ts:
@NgModule({
  imports: [
    CommonModule,
    AnalyzeOverviewRoutingModule,
    FeatureFlagModule,
    FormsModule,
    EditSpaceDescriptionWidgetModule,
    AnalyticalReportWidgetModule,
    CreateWorkItemWidgetModule,
    AddCodebaseWidgetModule,
    PipelinesWidgetModule,
    EnvironmentWidgetModule,
    ForgeWizardModule,
    ModalModule.forRoot()
  ],
  declarations: [AnalyzeOverviewComponent],
  entryComponents: [EnvironmentWidgetComponent]
})
export class AnalyzeOverviewModule {
  constructor(http: Http) { }
}

Because we use Feature-flag, we've also added FeatureFlagModule in the imports of the module.

Note: For more information on entryComponents read angular docs on entryComponents and dynamic component load.

  • Tell fabric8-ui which feature match which component. In src/app/feature-flag.mapping.ts, add a case in convertFeatureNameToComponent method:
  convertFeatureNameToComponent(name: string): Type<any> {
    switch (name) {
      case 'Environment.Test': {
        return EnvironmentWidgetComponent;
      }
      default: {
        return null;
      }
    }
  }

Demo

Better than just usage you want to see it in action? run the demo:

  • with direct access to prod-preview uncomment
  • with mock data (default option)
npm run build:demo
npm run start:demo

Go to http://localhost:8001

Release

  • pre-requisites Login to npmjs central repo with your credential (you should be owner of the library).

  • build ngx-feature-flag as a npm library

npm run build   
  • publish
npm publish dist

Note: semantic release are done via fabric8cd using semantic-release