@featurevisor/site 0.55.0 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintcache +1 -1
- package/.eslintrc.js +1 -0
- package/CHANGELOG.md +19 -0
- package/dist/favicon-128.png +0 -0
- package/dist/index.html +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/lib/components/SearchInput.d.ts +2 -7
- package/lib/hooks/useSearch.d.ts +16 -0
- package/lib/hooks/useSearchIndex.d.ts +4 -0
- package/lib/index.d.ts +1 -1
- package/lib/utils/index.d.ts +1 -1
- package/mock/search-index.json +278 -0
- package/package.json +12 -8
- package/postcss.config.js +6 -0
- package/public/index.html +1 -4
- package/src/components/HistoryTimeline.tsx +1 -1
- package/src/components/ListAttributes.tsx +3 -11
- package/src/components/ListFeatures.tsx +3 -11
- package/src/components/ListSegments.tsx +3 -11
- package/src/components/SearchInput.tsx +6 -8
- package/src/components/ShowAttribute.tsx +1 -1
- package/src/components/ShowFeature.tsx +1 -1
- package/src/components/ShowSegment.tsx +1 -1
- package/src/hooks/useSearch.ts +41 -0
- package/src/hooks/useSearchIndex.ts +12 -0
- package/src/index.tsx +5 -3
- package/src/utils/index.ts +2 -2
- package/tailwind.config.js +2 -2
- package/tsconfig.cjs.json +1 -1
- package/types/assets.d.ts +27 -0
- package/types/styles.d.ts +4 -0
- package/{webpack.config.js → webpack.common.js} +10 -2
- package/webpack.dev.js +27 -0
- package/webpack.prod.js +20 -0
- package/dist/index.css +0 -2183
- package/lib/hooks/searchIndexHook.d.ts +0 -1
- package/src/hooks/searchIndexHook.ts +0 -9
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
value: string;
|
|
4
|
-
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
5
|
-
}
|
|
6
|
-
export declare function SearchInput(props: SearchInputProps): JSX.Element;
|
|
7
|
-
export {};
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
export declare function SearchInput(): JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function useSearch(): {
|
|
2
|
+
readonly searchQuery: string;
|
|
3
|
+
readonly features: (import("@featurevisor/types").ParsedFeature & {
|
|
4
|
+
lastModified?: import("@featurevisor/types").LastModified;
|
|
5
|
+
})[];
|
|
6
|
+
readonly segments: (import("@featurevisor/types").Segment & {
|
|
7
|
+
lastModified?: import("@featurevisor/types").LastModified;
|
|
8
|
+
usedInFeatures: string[];
|
|
9
|
+
})[];
|
|
10
|
+
readonly attributes: (import("@featurevisor/types").Attribute & {
|
|
11
|
+
lastModified?: import("@featurevisor/types").LastModified;
|
|
12
|
+
usedInSegments: string[];
|
|
13
|
+
usedInFeatures: string[];
|
|
14
|
+
})[];
|
|
15
|
+
readonly setSearchQuery: (value: any) => void;
|
|
16
|
+
};
|
package/lib/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import "./index.css";
|
package/lib/utils/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface Query {
|
|
|
6
6
|
archived?: boolean;
|
|
7
7
|
capture?: boolean;
|
|
8
8
|
}
|
|
9
|
-
export declare function
|
|
9
|
+
export declare function parseSearchQuery(queryString: string): Query;
|
|
10
10
|
export declare function isEnabledInEnvironment(feature: any, environment: string): boolean;
|
|
11
11
|
export declare function isEnabledInAnyEnvironment(feature: any): boolean;
|
|
12
12
|
export declare function getFeaturesByQuery(query: Query, data: SearchIndex): (import("@featurevisor/types").ParsedFeature & {
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entities": {
|
|
3
|
+
"attributes": [
|
|
4
|
+
{
|
|
5
|
+
"archived": false,
|
|
6
|
+
"description": "country code in lower case (two lettered)",
|
|
7
|
+
"type": "string",
|
|
8
|
+
"key": "country",
|
|
9
|
+
"usedInFeatures": [],
|
|
10
|
+
"usedInSegments": ["germany", "netherlands", "switzerland"]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"archived": false,
|
|
14
|
+
"description": "device type",
|
|
15
|
+
"type": "string",
|
|
16
|
+
"key": "device",
|
|
17
|
+
"usedInFeatures": [],
|
|
18
|
+
"usedInSegments": ["mobile"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"archived": false,
|
|
22
|
+
"description": "is the user already logged in?",
|
|
23
|
+
"type": "boolean",
|
|
24
|
+
"key": "loggedIn",
|
|
25
|
+
"usedInFeatures": [],
|
|
26
|
+
"usedInSegments": []
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"archived": false,
|
|
30
|
+
"description": "User ID",
|
|
31
|
+
"type": "string",
|
|
32
|
+
"capture": true,
|
|
33
|
+
"key": "userId",
|
|
34
|
+
"usedInFeatures": ["foo"],
|
|
35
|
+
"usedInSegments": []
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"segments": [
|
|
39
|
+
{
|
|
40
|
+
"archived": false,
|
|
41
|
+
"description": "users from Germany",
|
|
42
|
+
"conditions": { "and": [{ "attribute": "country", "operator": "equals", "value": "de" }] },
|
|
43
|
+
"key": "germany",
|
|
44
|
+
"usedInFeatures": ["bar", "foo", "sidebar"]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"archived": false,
|
|
48
|
+
"description": "mobile users",
|
|
49
|
+
"conditions": {
|
|
50
|
+
"and": [{ "attribute": "device", "operator": "equals", "value": "mobile" }]
|
|
51
|
+
},
|
|
52
|
+
"key": "mobile",
|
|
53
|
+
"usedInFeatures": ["foo"]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"archived": false,
|
|
57
|
+
"description": "The Netherlands",
|
|
58
|
+
"conditions": [{ "attribute": "country", "operator": "equals", "value": "nl" }],
|
|
59
|
+
"key": "netherlands",
|
|
60
|
+
"usedInFeatures": ["sidebar"]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"archived": false,
|
|
64
|
+
"description": "users from Switzerland",
|
|
65
|
+
"conditions": { "and": [{ "attribute": "country", "operator": "equals", "value": "ch" }] },
|
|
66
|
+
"key": "switzerland",
|
|
67
|
+
"usedInFeatures": ["bar", "foo"]
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"features": [
|
|
71
|
+
{
|
|
72
|
+
"tags": ["all"],
|
|
73
|
+
"bucketBy": "userId",
|
|
74
|
+
"defaultVariation": "control",
|
|
75
|
+
"variablesSchema": [
|
|
76
|
+
{ "key": "color", "type": "string", "defaultValue": "red" },
|
|
77
|
+
{
|
|
78
|
+
"key": "hero",
|
|
79
|
+
"type": "object",
|
|
80
|
+
"defaultValue": {
|
|
81
|
+
"title": "Hero Title",
|
|
82
|
+
"subtitle": "Hero Subtitle",
|
|
83
|
+
"alignment": "center"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"variations": [
|
|
88
|
+
{ "type": "string", "value": "control", "weight": 33 },
|
|
89
|
+
{
|
|
90
|
+
"type": "string",
|
|
91
|
+
"value": "b",
|
|
92
|
+
"weight": 33,
|
|
93
|
+
"variables": [
|
|
94
|
+
{
|
|
95
|
+
"key": "hero",
|
|
96
|
+
"value": {
|
|
97
|
+
"title": "Hero Title for B",
|
|
98
|
+
"subtitle": "Hero Subtitle for B",
|
|
99
|
+
"alignment": "center for B"
|
|
100
|
+
},
|
|
101
|
+
"overrides": [
|
|
102
|
+
{
|
|
103
|
+
"segments": { "or": ["germany", "switzerland"] },
|
|
104
|
+
"value": {
|
|
105
|
+
"title": "Hero Title for B in DE or CH",
|
|
106
|
+
"subtitle": "Hero Subtitle for B in DE of CH",
|
|
107
|
+
"alignment": "center for B in DE or CH"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{ "type": "string", "value": "c", "weight": 34 }
|
|
115
|
+
],
|
|
116
|
+
"environments": {
|
|
117
|
+
"staging": { "rules": [{ "key": "1", "segments": "*", "percentage": 100 }] },
|
|
118
|
+
"production": { "rules": [{ "key": "1", "segments": "*", "percentage": 100 }] }
|
|
119
|
+
},
|
|
120
|
+
"key": "bar"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"description": "Classic on/off switch",
|
|
124
|
+
"tags": ["all"],
|
|
125
|
+
"defaultVariation": false,
|
|
126
|
+
"bucketBy": "userId",
|
|
127
|
+
"variations": [
|
|
128
|
+
{ "description": "Enabled for all", "type": "boolean", "value": true, "weight": 100 },
|
|
129
|
+
{ "description": "Disabled for all", "type": "boolean", "value": false, "weight": 0 }
|
|
130
|
+
],
|
|
131
|
+
"environments": {
|
|
132
|
+
"staging": { "rules": [{ "key": "1", "segments": "*", "percentage": 100 }] },
|
|
133
|
+
"production": { "rules": [{ "key": "1", "segments": "*", "percentage": 80 }] }
|
|
134
|
+
},
|
|
135
|
+
"key": "baz"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"archived": false,
|
|
139
|
+
"description": "blah",
|
|
140
|
+
"tags": ["all", "signIn", "signUp"],
|
|
141
|
+
"bucketBy": "userId",
|
|
142
|
+
"defaultVariation": false,
|
|
143
|
+
"variablesSchema": [
|
|
144
|
+
{ "key": "bar", "type": "string", "defaultValue": "" },
|
|
145
|
+
{ "key": "baz", "type": "string", "defaultValue": "" }
|
|
146
|
+
],
|
|
147
|
+
"variations": [
|
|
148
|
+
{ "type": "boolean", "value": false, "weight": 50 },
|
|
149
|
+
{
|
|
150
|
+
"type": "boolean",
|
|
151
|
+
"value": true,
|
|
152
|
+
"weight": 50,
|
|
153
|
+
"variables": [
|
|
154
|
+
{
|
|
155
|
+
"key": "bar",
|
|
156
|
+
"value": "bar_here",
|
|
157
|
+
"overrides": [
|
|
158
|
+
{ "segments": { "or": ["germany", "switzerland"] }, "value": "bar for DE or CH" }
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
{ "key": "baz", "value": "baz_here" }
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
"environments": {
|
|
166
|
+
"staging": {
|
|
167
|
+
"expose": true,
|
|
168
|
+
"rules": [{ "key": "1", "segments": "*", "percentage": 100 }]
|
|
169
|
+
},
|
|
170
|
+
"production": {
|
|
171
|
+
"expose": true,
|
|
172
|
+
"rules": [
|
|
173
|
+
{
|
|
174
|
+
"key": "1",
|
|
175
|
+
"segments": { "and": ["mobile", { "or": ["germany", "switzerland"] }] },
|
|
176
|
+
"percentage": 80
|
|
177
|
+
},
|
|
178
|
+
{ "key": "2", "segments": "*", "percentage": 50 }
|
|
179
|
+
],
|
|
180
|
+
"force": [
|
|
181
|
+
{
|
|
182
|
+
"conditions": {
|
|
183
|
+
"and": [
|
|
184
|
+
{ "attribute": "userId", "operator": "equals", "value": "123" },
|
|
185
|
+
{ "attribute": "deviceId", "operator": "equals", "value": "234" }
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
"variation": true,
|
|
189
|
+
"variables": { "bar": "yoooooo" }
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
"key": "foo"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"description": "Variations with weights having decimal places",
|
|
198
|
+
"tags": ["all"],
|
|
199
|
+
"defaultVariation": "control",
|
|
200
|
+
"bucketBy": "userId",
|
|
201
|
+
"variablesSchema": [
|
|
202
|
+
{ "type": "json", "key": "fooConfig", "defaultValue": "{\"foo\": \"bar\"}" }
|
|
203
|
+
],
|
|
204
|
+
"variations": [
|
|
205
|
+
{ "type": "string", "value": "control", "weight": 33.34 },
|
|
206
|
+
{
|
|
207
|
+
"type": "string",
|
|
208
|
+
"value": "b",
|
|
209
|
+
"weight": 33.33,
|
|
210
|
+
"variables": [{ "key": "fooConfig", "value": "{\"foo\": \"bar b\"}" }]
|
|
211
|
+
},
|
|
212
|
+
{ "type": "string", "value": "c", "weight": 33.33 }
|
|
213
|
+
],
|
|
214
|
+
"environments": {
|
|
215
|
+
"staging": { "rules": [{ "key": "1", "segments": "*", "percentage": 100 }] },
|
|
216
|
+
"production": { "rules": [{ "key": "1", "segments": "*", "percentage": 100 }] }
|
|
217
|
+
},
|
|
218
|
+
"key": "qux"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"description": "Show sidebar or not",
|
|
222
|
+
"tags": ["all"],
|
|
223
|
+
"bucketBy": "userId",
|
|
224
|
+
"defaultVariation": false,
|
|
225
|
+
"variablesSchema": [
|
|
226
|
+
{ "key": "position", "type": "string", "defaultValue": "left" },
|
|
227
|
+
{ "key": "color", "type": "string", "defaultValue": "red" },
|
|
228
|
+
{ "key": "sections", "type": "array", "defaultValue": [] },
|
|
229
|
+
{ "key": "title", "type": "string", "defaultValue": "Sidebar Title" }
|
|
230
|
+
],
|
|
231
|
+
"variations": [
|
|
232
|
+
{ "type": "boolean", "value": false, "weight": 10 },
|
|
233
|
+
{
|
|
234
|
+
"type": "boolean",
|
|
235
|
+
"value": true,
|
|
236
|
+
"weight": 90,
|
|
237
|
+
"variables": [
|
|
238
|
+
{ "key": "position", "value": "right" },
|
|
239
|
+
{
|
|
240
|
+
"key": "color",
|
|
241
|
+
"value": "red",
|
|
242
|
+
"overrides": [
|
|
243
|
+
{ "segments": "germany", "value": "yellow" },
|
|
244
|
+
{ "segments": "japan", "value": "white" }
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"key": "sections",
|
|
249
|
+
"value": ["home", "about", "contact"],
|
|
250
|
+
"overrides": [
|
|
251
|
+
{ "segments": "germany", "value": ["home", "about", "contact", "imprint"] },
|
|
252
|
+
{
|
|
253
|
+
"segments": "netherlands",
|
|
254
|
+
"value": ["home", "about", "contact", "bitterballen"]
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
]
|
|
259
|
+
}
|
|
260
|
+
],
|
|
261
|
+
"environments": {
|
|
262
|
+
"staging": { "rules": [{ "key": "1", "segments": "*", "percentage": 100 }] },
|
|
263
|
+
"production": {
|
|
264
|
+
"rules": [
|
|
265
|
+
{
|
|
266
|
+
"key": "1",
|
|
267
|
+
"segments": "*",
|
|
268
|
+
"percentage": 100,
|
|
269
|
+
"variables": { "title": "Sidebar Title for production" }
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
"key": "sidebar"
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@featurevisor/site",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"description": "Static site for Featurevisor",
|
|
5
5
|
"main": "dist",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"transpile": "echo 'Nothing to transpile in this package'",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"dist": "npm run dist:js && npm run dist:css",
|
|
11
|
-
"build": "npm run dist",
|
|
8
|
+
"build": "webpack --mode production --config ./webpack.prod.js",
|
|
9
|
+
"dev": "webpack serve --mode development --config ./webpack.dev.js",
|
|
12
10
|
"test": "echo 'Nothing to test in types package'",
|
|
13
11
|
"format": "prettier . --check --cache --loglevel=warn",
|
|
14
12
|
"lint": "eslint . --cache",
|
|
@@ -50,16 +48,22 @@
|
|
|
50
48
|
"@tailwindcss/typography": "^0.5.9",
|
|
51
49
|
"@types/react": "^18.0.29",
|
|
52
50
|
"@types/react-dom": "^18.0.11",
|
|
51
|
+
"autoprefixer": "^10.4.16",
|
|
52
|
+
"copy-webpack-plugin": "^11.0.0",
|
|
53
53
|
"css-loader": "^6.7.3",
|
|
54
|
+
"html-webpack-plugin": "^5.5.3",
|
|
55
|
+
"postcss": "^8.4.31",
|
|
56
|
+
"postcss-loader": "^7.3.3",
|
|
54
57
|
"react": "^18.2.0",
|
|
55
58
|
"react-dom": "^18.2.0",
|
|
56
59
|
"react-markdown": "^8.0.6",
|
|
57
60
|
"react-router-dom": "^6.10.0",
|
|
58
61
|
"style-loader": "^3.3.2",
|
|
59
|
-
"tailwindcss": "^3.3.
|
|
62
|
+
"tailwindcss": "^3.3.3",
|
|
63
|
+
"webpack-merge": "^5.10.0"
|
|
60
64
|
},
|
|
61
65
|
"dependencies": {
|
|
62
|
-
"@featurevisor/types": "^0.
|
|
66
|
+
"@featurevisor/types": "^0.56.0"
|
|
63
67
|
},
|
|
64
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "cf164942d23745dbf52e8b68abbe7b153891ba11"
|
|
65
69
|
}
|
package/public/index.html
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<title>Featurevisor Status</title>
|
|
6
|
-
<link rel="stylesheet" href="./index.css" />
|
|
7
6
|
<link rel="icon" type="image/png" href="./favicon-128.png" />
|
|
8
7
|
</head>
|
|
9
8
|
|
|
10
9
|
<body>
|
|
11
10
|
<div id="root"></div>
|
|
12
|
-
|
|
13
|
-
<script src="./index.js"></script>
|
|
14
11
|
</body>
|
|
15
12
|
</html>
|
|
@@ -4,7 +4,7 @@ import { UserCircleIcon } from "@heroicons/react/20/solid";
|
|
|
4
4
|
|
|
5
5
|
import { Alert } from "./Alert";
|
|
6
6
|
import { PrettyDate } from "./PrettyDate";
|
|
7
|
-
import { useSearchIndex } from "../hooks/
|
|
7
|
+
import { useSearchIndex } from "../hooks/useSearchIndex";
|
|
8
8
|
|
|
9
9
|
const entriesPerPage = 50;
|
|
10
10
|
const initialMaxEntitiesCount = 10;
|
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Link } from "react-router-dom";
|
|
3
3
|
|
|
4
|
-
import { SearchIndex } from "@featurevisor/types";
|
|
5
|
-
import { useSearchIndex } from "../hooks/searchIndexHook";
|
|
6
|
-
import { getQueryFromString, getAttributesByQuery } from "../utils";
|
|
7
4
|
import { Tag } from "./Tag";
|
|
8
5
|
import { Alert } from "./Alert";
|
|
9
6
|
import { SearchInput } from "./SearchInput";
|
|
10
7
|
import { PageTitle } from "./PageTitle";
|
|
11
8
|
import { PageContent } from "./PageContent";
|
|
12
9
|
import { LastModified } from "./LastModified";
|
|
10
|
+
import { useSearch } from "../hooks/useSearch";
|
|
13
11
|
|
|
14
12
|
export function ListAttributes() {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const contextValue = useSearchIndex();
|
|
18
|
-
const data = contextValue.data as SearchIndex;
|
|
19
|
-
|
|
20
|
-
const query = getQueryFromString(q);
|
|
21
|
-
const attributes = getAttributesByQuery(query, data);
|
|
13
|
+
const { attributes } = useSearch();
|
|
22
14
|
|
|
23
15
|
return (
|
|
24
16
|
<PageContent>
|
|
25
17
|
<PageTitle>Attributes</PageTitle>
|
|
26
18
|
|
|
27
|
-
<SearchInput
|
|
19
|
+
<SearchInput />
|
|
28
20
|
|
|
29
21
|
{attributes.length === 0 && <Alert type="warning">No results found</Alert>}
|
|
30
22
|
|
|
@@ -3,9 +3,6 @@ import { Link } from "react-router-dom";
|
|
|
3
3
|
|
|
4
4
|
import { TagIcon } from "@heroicons/react/20/solid";
|
|
5
5
|
|
|
6
|
-
import { SearchIndex } from "@featurevisor/types";
|
|
7
|
-
import { useSearchIndex } from "../hooks/searchIndexHook";
|
|
8
|
-
import { getQueryFromString, getFeaturesByQuery } from "../utils";
|
|
9
6
|
import { EnvironmentDot } from "./EnvironmentDot";
|
|
10
7
|
import { Tag } from "./Tag";
|
|
11
8
|
import { Alert } from "./Alert";
|
|
@@ -13,21 +10,16 @@ import { SearchInput } from "./SearchInput";
|
|
|
13
10
|
import { PageTitle } from "./PageTitle";
|
|
14
11
|
import { PageContent } from "./PageContent";
|
|
15
12
|
import { LastModified } from "./LastModified";
|
|
13
|
+
import { useSearch } from "../hooks/useSearch";
|
|
16
14
|
|
|
17
15
|
export function ListFeatures() {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const contextValue = useSearchIndex();
|
|
21
|
-
const data = contextValue.data as SearchIndex;
|
|
22
|
-
|
|
23
|
-
const query = getQueryFromString(q);
|
|
24
|
-
const features = getFeaturesByQuery(query, data);
|
|
16
|
+
const { features } = useSearch();
|
|
25
17
|
|
|
26
18
|
return (
|
|
27
19
|
<PageContent>
|
|
28
20
|
<PageTitle>Features</PageTitle>
|
|
29
21
|
|
|
30
|
-
<SearchInput
|
|
22
|
+
<SearchInput />
|
|
31
23
|
|
|
32
24
|
{features.length === 0 && <Alert type="warning">No results found</Alert>}
|
|
33
25
|
|
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Link } from "react-router-dom";
|
|
3
3
|
|
|
4
|
-
import { SearchIndex } from "@featurevisor/types";
|
|
5
|
-
import { useSearchIndex } from "../hooks/searchIndexHook";
|
|
6
|
-
import { getQueryFromString, getSegmentsByQuery } from "../utils";
|
|
7
4
|
import { Tag } from "./Tag";
|
|
8
5
|
import { Alert } from "./Alert";
|
|
9
6
|
import { SearchInput } from "./SearchInput";
|
|
10
7
|
import { PageTitle } from "./PageTitle";
|
|
11
8
|
import { PageContent } from "./PageContent";
|
|
12
9
|
import { LastModified } from "./LastModified";
|
|
10
|
+
import { useSearch } from "../hooks/useSearch";
|
|
13
11
|
|
|
14
12
|
export function ListSegments() {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const contextValue = useSearchIndex();
|
|
18
|
-
const data = contextValue.data as SearchIndex;
|
|
19
|
-
|
|
20
|
-
const query = getQueryFromString(q);
|
|
21
|
-
const segments = getSegmentsByQuery(query, data);
|
|
13
|
+
const { segments } = useSearch();
|
|
22
14
|
|
|
23
15
|
return (
|
|
24
16
|
<PageContent>
|
|
25
17
|
<PageTitle>Segments</PageTitle>
|
|
26
18
|
|
|
27
|
-
<SearchInput
|
|
19
|
+
<SearchInput />
|
|
28
20
|
|
|
29
21
|
{segments.length === 0 && <Alert type="warning">No results found</Alert>}
|
|
30
22
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { useSearch } from "../hooks/useSearch";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
6
|
-
}
|
|
4
|
+
export function SearchInput() {
|
|
5
|
+
const { searchQuery, setSearchQuery } = useSearch();
|
|
7
6
|
|
|
8
|
-
export function SearchInput(props: SearchInputProps) {
|
|
9
7
|
return (
|
|
10
8
|
<div className="relative px-6 pt-3.5">
|
|
11
9
|
<div className="pointer-events-none absolute">
|
|
@@ -23,11 +21,11 @@ export function SearchInput(props: SearchInputProps) {
|
|
|
23
21
|
</div>
|
|
24
22
|
<input
|
|
25
23
|
type="text"
|
|
26
|
-
value={
|
|
27
|
-
onChange={(e) =>
|
|
24
|
+
value={searchQuery}
|
|
25
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
28
26
|
placeholder="Type to search..."
|
|
29
27
|
autoComplete="off"
|
|
30
|
-
className="mb-4 mt-2 w-full rounded-full border-slate-300 indent-8 text-xl text-gray-700 placeholder:text-gray-400"
|
|
28
|
+
className="mb-4 mt-2 p-2 w-full rounded-full border border-slate-300 indent-8 text-xl text-gray-700 placeholder:text-gray-400"
|
|
31
29
|
/>
|
|
32
30
|
</div>
|
|
33
31
|
);
|
|
@@ -5,7 +5,7 @@ import { PageContent } from "./PageContent";
|
|
|
5
5
|
import { PageTitle } from "./PageTitle";
|
|
6
6
|
import { Tabs } from "./Tabs";
|
|
7
7
|
import { EditLink } from "./EditLink";
|
|
8
|
-
import { useSearchIndex } from "../hooks/
|
|
8
|
+
import { useSearchIndex } from "../hooks/useSearchIndex";
|
|
9
9
|
import { Markdown } from "./Markdown";
|
|
10
10
|
import { HistoryTimeline } from "./HistoryTimeline";
|
|
11
11
|
|
|
@@ -6,7 +6,7 @@ import { PageTitle } from "./PageTitle";
|
|
|
6
6
|
import { Tabs } from "./Tabs";
|
|
7
7
|
import { HistoryTimeline } from "./HistoryTimeline";
|
|
8
8
|
import { Tag } from "./Tag";
|
|
9
|
-
import { useSearchIndex } from "../hooks/
|
|
9
|
+
import { useSearchIndex } from "../hooks/useSearchIndex";
|
|
10
10
|
import { isEnabledInEnvironment } from "../utils";
|
|
11
11
|
import { ExpandRuleSegments } from "./ExpandRuleSegments";
|
|
12
12
|
import { ExpandConditions } from "./ExpandConditions";
|
|
@@ -7,7 +7,7 @@ import { Tabs } from "./Tabs";
|
|
|
7
7
|
import { HistoryTimeline } from "./HistoryTimeline";
|
|
8
8
|
import { ExpandConditions } from "./ExpandConditions";
|
|
9
9
|
import { EditLink } from "./EditLink";
|
|
10
|
-
import { useSearchIndex } from "../hooks/
|
|
10
|
+
import { useSearchIndex } from "../hooks/useSearchIndex";
|
|
11
11
|
import { Markdown } from "./Markdown";
|
|
12
12
|
|
|
13
13
|
export function DisplaySegmentOverview() {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useSearchParams } from "react-router-dom";
|
|
3
|
+
|
|
4
|
+
import { useSearchIndex } from "./useSearchIndex";
|
|
5
|
+
import {
|
|
6
|
+
parseSearchQuery,
|
|
7
|
+
getFeaturesByQuery,
|
|
8
|
+
getAttributesByQuery,
|
|
9
|
+
getSegmentsByQuery,
|
|
10
|
+
} from "../utils";
|
|
11
|
+
|
|
12
|
+
const SEARCH_QUERY_KEY = "q";
|
|
13
|
+
|
|
14
|
+
export function useSearch() {
|
|
15
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
16
|
+
const searchQuery = String(searchParams.get(SEARCH_QUERY_KEY) || "");
|
|
17
|
+
|
|
18
|
+
const { data: searchData } = useSearchIndex();
|
|
19
|
+
const parsedQuery = parseSearchQuery(searchQuery);
|
|
20
|
+
const features = getFeaturesByQuery(parsedQuery, searchData);
|
|
21
|
+
const segments = getSegmentsByQuery(parsedQuery, searchData);
|
|
22
|
+
const attributes = getAttributesByQuery(parsedQuery, searchData);
|
|
23
|
+
|
|
24
|
+
const setSearchQuery = useCallback((value) => {
|
|
25
|
+
const newSearchParams = new URLSearchParams(searchParams.toString());
|
|
26
|
+
if (value) {
|
|
27
|
+
newSearchParams.set(SEARCH_QUERY_KEY, String(value));
|
|
28
|
+
} else {
|
|
29
|
+
newSearchParams.delete(SEARCH_QUERY_KEY);
|
|
30
|
+
}
|
|
31
|
+
setSearchParams(newSearchParams);
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
searchQuery,
|
|
36
|
+
features,
|
|
37
|
+
segments,
|
|
38
|
+
attributes,
|
|
39
|
+
setSearchQuery,
|
|
40
|
+
} as const;
|
|
41
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { SearchIndex } from "@featurevisor/types";
|
|
3
|
+
|
|
4
|
+
import { SearchIndexContext } from "../contexts/SearchIndexContext";
|
|
5
|
+
|
|
6
|
+
export function useSearchIndex() {
|
|
7
|
+
const { data } = useContext(SearchIndexContext);
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
data: data as SearchIndex,
|
|
11
|
+
};
|
|
12
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
3
|
import { HashRouter } from "react-router-dom";
|
|
4
|
+
import "./index.css";
|
|
4
5
|
|
|
5
6
|
import { App } from "./components/App";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
const container = document.getElementById("root");
|
|
9
|
+
const root = createRoot(container!);
|
|
10
|
+
root.render(
|
|
8
11
|
<HashRouter>
|
|
9
12
|
<App />
|
|
10
13
|
</HashRouter>,
|
|
11
|
-
document.getElementById("root"),
|
|
12
14
|
);
|
package/src/utils/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface Query {
|
|
|
8
8
|
capture?: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function
|
|
11
|
+
export function parseSearchQuery(queryString: string) {
|
|
12
12
|
const query: Query = {
|
|
13
13
|
keyword: "",
|
|
14
14
|
tags: [],
|
|
@@ -17,7 +17,7 @@ export function getQueryFromString(q: string) {
|
|
|
17
17
|
capture: undefined,
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
const parts =
|
|
20
|
+
const parts = queryString.split(" ");
|
|
21
21
|
|
|
22
22
|
for (const part of parts) {
|
|
23
23
|
if (part.startsWith("tag:")) {
|