mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 18:16:15 +08:00
init: the first public commit for AFFiNE
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nrwl/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
# Feature Flags
|
||||
|
||||
## Usage
|
||||
|
||||
- set provider
|
||||
|
||||
```tsx
|
||||
import { FeatureFlagsProvider } from '@toeverything/datasource/feature-flags';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<FeatureFlagsProvider>
|
||||
<Page />
|
||||
</FeatureFlagsProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
- use flag
|
||||
|
||||
```ts
|
||||
import { useFlag } from '@toeverything/datasource/feature-flags';
|
||||
|
||||
const App = () => {
|
||||
// flag key, default value
|
||||
const flag1 = useFlag('flag1', false);
|
||||
// ^? boolean
|
||||
const flag2 = useFlag<boolean>('flag2');
|
||||
// ^? boolean | undefined
|
||||
return flag1 && <div>The flag1 is enable!</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## How to add a new feature flag?
|
||||
|
||||
- Enter [Portal](https://portal.featureflag.co/switch-manage)
|
||||
- Click `New Switch` button
|
||||
- Input `Switch Name`, and Enter
|
||||
- Adjust data and save
|
||||
|
||||
# Test flags
|
||||
|
||||
**When entering development mode feature flag will NOT be updated in real time**
|
||||
|
||||
- `activateFfcDevMode()` play with feature flags locally
|
||||
- `quitFfcDevMode()` quit dev mode
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test datasource/feature-flags` to execute the unit tests via [Jest](https://jestjs.io).
|
||||
|
||||
## References
|
||||
|
||||
- [Feature Flag Portal](https://portal.featureflag.co/)
|
||||
- [Feature Flag React Web APP DEMO](https://featureflag.moyincloud.com/ksrm/React_Web_APP.html)
|
||||
@@ -0,0 +1,10 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'datasource-feature-flags',
|
||||
preset: '../../../jest.preset.js',
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../../coverage/libs/datasource/feature-flags',
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@toeverything/datasource/feature-flags",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ffc-js-client-side-sdk": "^1.1.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/datasource/feature-flags/src",
|
||||
"projectType": "library",
|
||||
"tags": ["library:feature-flags"],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nrwl/web:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/datasource/feature-flags",
|
||||
"tsConfig": "libs/datasource/feature-flags/tsconfig.lib.json",
|
||||
"project": "libs/datasource/feature-flags/package.json",
|
||||
"entryFile": "libs/datasource/feature-flags/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/datasource/feature-flags/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"libs/datasource/feature-flags/**/*.{ts,tsx,js,jsx}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["coverage/libs/datasource/feature-flags"],
|
||||
"options": {
|
||||
"jestConfig": "libs/datasource/feature-flags/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import ffcClient, { Ffc } from 'ffc-js-client-side-sdk';
|
||||
import type {
|
||||
IFeatureFlagSet,
|
||||
IOption,
|
||||
} from 'ffc-js-client-side-sdk/esm/types';
|
||||
import { createContext, ReactNode, useEffect, useState } from 'react';
|
||||
import { config } from './config';
|
||||
|
||||
/**
|
||||
* Init `ffcClient`
|
||||
* Ported from https://github.com/feature-flags-co/ffc-js-client-side-sdk-react-jotai-demo/blob/main/src/ffc/hooks.ts
|
||||
* @private
|
||||
*/
|
||||
export const useInitFfcEffect = (featureFlagClient: Ffc, option: IOption) => {
|
||||
const [flags, setFlags] = useState<Record<string, IFeatureFlagSet>>({});
|
||||
|
||||
useEffect(() => {
|
||||
featureFlagClient.init(option);
|
||||
|
||||
featureFlagClient.on('ff_update', (changes: unknown[]) => {
|
||||
if (changes.length) {
|
||||
setFlags(featureFlagClient.getAllFeatureFlags());
|
||||
}
|
||||
});
|
||||
|
||||
featureFlagClient.waitUntilReady().then((data: unknown[]) => {
|
||||
if (data.length) {
|
||||
setFlags(featureFlagClient.getAllFeatureFlags());
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
// FIX destroy ffcClient
|
||||
// ffcClient.logout();
|
||||
};
|
||||
}, [featureFlagClient, option, setFlags]);
|
||||
return flags;
|
||||
};
|
||||
|
||||
export const FeatureFlagsContext = createContext<{
|
||||
ffcClient: Ffc;
|
||||
flags: IFeatureFlagSet;
|
||||
// TODO use genErrorObj
|
||||
}>({} as any);
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const App = () => (
|
||||
* <FeatureFlagsProvider>
|
||||
* <Page />
|
||||
* </FeatureFlagsProvider>
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export const FeatureFlagsProvider = ({ children }: { children: ReactNode }) => {
|
||||
const flags = useInitFfcEffect(ffcClient, config);
|
||||
|
||||
return (
|
||||
<FeatureFlagsContext.Provider value={{ ffcClient, flags }}>
|
||||
{children}
|
||||
</FeatureFlagsContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { IOption } from 'ffc-js-client-side-sdk/esm/types';
|
||||
|
||||
export const config: IOption = {
|
||||
secret: process.env['AFFINE_FEATURE_FLAG_TOKEN'],
|
||||
anonymous: true,
|
||||
// user: {
|
||||
// userName: 'the user's user name',
|
||||
// id: 'the user's unique identifier'
|
||||
// }
|
||||
devModePassword: '-',
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
// Feature Flags
|
||||
// Website https://featureflag.co/
|
||||
// SDK doc https://github.com/feature-flags-co/ffc-js-client-side-sdk
|
||||
// Demo https://github.com/feature-flags-co/ffc-js-client-side-sdk-react-jotai-demo
|
||||
|
||||
import type { IUser } from 'ffc-js-client-side-sdk/esm/types';
|
||||
import { useContext } from 'react';
|
||||
import { FeatureFlagsContext } from './Context';
|
||||
|
||||
/**
|
||||
* The `ffcClient` should not export to external modules.
|
||||
* Do not export this please.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const useFfcClient = () => {
|
||||
const { ffcClient } = useContext(FeatureFlagsContext);
|
||||
return ffcClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* NOTICE: The hook can no trigger the `Ffc.sendFeatureFlagInsight`,
|
||||
* may lead to inaccurate telemetry data.
|
||||
*
|
||||
* Use it discreetly.
|
||||
*/
|
||||
const useFlags = () => {
|
||||
const { flags } = useContext(FeatureFlagsContext);
|
||||
return flags;
|
||||
};
|
||||
|
||||
type UseFlagFn = (<T = unknown>(flag: string) => T | undefined) &
|
||||
(<T = unknown>(flag: string, defaultValue: T) => T) &
|
||||
// Workaround for infers boolean as true or false
|
||||
// Remove this after the issue fixed
|
||||
// See https://github.com/microsoft/TypeScript/issues/29400
|
||||
((flag: string, defaultValue: false) => boolean) &
|
||||
((flag: string, defaultValue: true) => boolean);
|
||||
|
||||
/**
|
||||
* Returns the specified flag.
|
||||
*
|
||||
* The parameter defaultValue should have the same data type with flag type.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
* ```ts
|
||||
* const App = () => {
|
||||
* const flag1 = useFlag('flag1', 'default value');
|
||||
* // ^? string
|
||||
* const flag2 = useFlag<boolean>('flag2');
|
||||
* // ^? boolean | undefined
|
||||
* return <div>flag1: {flag1}</div>
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const useFlag = ((flag: string, defaultValue: unknown) => {
|
||||
const flags = useFlags();
|
||||
const ffcClient = useFfcClient();
|
||||
// @ts-expect-error This is a BUG for ffc-js-client. ffc-js-client supported typed flag now.
|
||||
ffcClient.sendFeatureFlagInsight(flag, defaultValue);
|
||||
const value: unknown = flags[flag];
|
||||
|
||||
if (defaultValue === undefined) {
|
||||
// Can not guess the type, return the flag value directly
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (typeof value !== typeof defaultValue) {
|
||||
console.error(
|
||||
`[Feature Flags] Flag "${flag}" type mismatch! Make ensure you have set the correct type in feature flag portal! flag type: "${typeof value}", defaultValue type: "${typeof defaultValue}"`,
|
||||
'flag:',
|
||||
value,
|
||||
'defaultValue:',
|
||||
defaultValue
|
||||
);
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}) as UseFlagFn;
|
||||
|
||||
/**
|
||||
* Set the user after initialization
|
||||
*
|
||||
* If the user parameter cannot be passed by the init method,
|
||||
* the following method can be used to set the user after initialization.
|
||||
*
|
||||
* See https://github.com/feature-flags-co/ffc-js-client-side-sdk#set-the-user-after-initialization
|
||||
*/
|
||||
export const useIdentifyUser = () => {
|
||||
const ffcClient = useFfcClient();
|
||||
return (user: IUser) => ffcClient.identify(user);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the user to anonymous user
|
||||
*
|
||||
* We can manually call the method logout, which will switch the current user back to anonymous user if exists already or create a new anonymous user.
|
||||
*
|
||||
* See https://github.com/feature-flags-co/ffc-js-client-side-sdk#set-the-user-to-anonymous-user
|
||||
*/
|
||||
export const useLogout = () => {
|
||||
const ffcClient = useFfcClient();
|
||||
return () => ffcClient.logout();
|
||||
};
|
||||
|
||||
export { FeatureFlagsProvider } from './Context';
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"importsNotUsedAsValues": "error"
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": [
|
||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"jest.config.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx"
|
||||
],
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.jsx",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user