@featurevisor/site 0.55.1 → 0.57.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.
@@ -1,7 +1,2 @@
1
- import * as React from "react";
2
- interface SearchInputProps {
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;
@@ -2,6 +2,7 @@
2
2
  interface Tab {
3
3
  title: string;
4
4
  to: string;
5
+ end?: boolean;
5
6
  }
6
7
  interface TabsProps {
7
8
  tabs: Tab[];
@@ -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
+ };
@@ -0,0 +1,4 @@
1
+ import { SearchIndex } from "@featurevisor/types";
2
+ export declare function useSearchIndex(): {
3
+ data: SearchIndex;
4
+ };
@@ -6,7 +6,7 @@ export interface Query {
6
6
  archived?: boolean;
7
7
  capture?: boolean;
8
8
  }
9
- export declare function getQueryFromString(q: string): Query;
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 & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featurevisor/site",
3
- "version": "0.55.1",
3
+ "version": "0.57.0",
4
4
  "description": "Static site for Featurevisor",
5
5
  "main": "dist",
6
6
  "scripts": {
@@ -63,7 +63,7 @@
63
63
  "webpack-merge": "^5.10.0"
64
64
  },
65
65
  "dependencies": {
66
- "@featurevisor/types": "^0.55.0"
66
+ "@featurevisor/types": "^0.57.0"
67
67
  },
68
- "gitHead": "8d1f0d66978e079b695537e5575a2f2773614d60"
68
+ "gitHead": "62bd5cfb1b6f2f40747ad3abb3aa2562b3ec8aa7"
69
69
  }
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
 
3
- import { Routes, Route, redirect } from "react-router-dom";
3
+ import { Routes, Route, Navigate } from "react-router-dom";
4
4
 
5
5
  import { Header } from "./Header";
6
6
  import { Footer } from "./Footer";
@@ -63,9 +63,6 @@ export function App() {
63
63
  {fetchedSearchIndex && (
64
64
  <SearchIndexContext.Provider value={{ isLoaded: true, data: fetchedSearchIndex }}>
65
65
  <Routes>
66
- {/* @TODO: try redirecting to /features */}
67
- <Route path="/" element={<ListFeatures />} />
68
-
69
66
  <Route path="features">
70
67
  <Route index element={<ListFeatures />} />
71
68
 
@@ -75,23 +72,11 @@ export function App() {
75
72
  <Route path="variables" element={<DisplayFeatureVariablesSchema />} />
76
73
  <Route path="rules" element={<DisplayFeatureRules />}>
77
74
  <Route path=":environmentKey" element={<DisplayFeatureRulesTable />} />
78
- <Route
79
- path="*"
80
- loader={({ params }) =>
81
- /* @TODO: fix redirection */
82
- redirect(`/features/${params.featureKey}/rules/${environmentKeys[0]}`)
83
- }
84
- />
75
+ <Route index element={<Navigate to={environmentKeys[0]} replace />} />
85
76
  </Route>
86
77
  <Route path="force" element={<DisplayFeatureForce />}>
87
78
  <Route path=":environmentKey" element={<DisplayFeatureForceTable />} />
88
- <Route
89
- path="*"
90
- loader={({ params }) =>
91
- /* @TODO: fix redirection */
92
- redirect(`/features/${params.featureKey}/force/${environmentKeys[0]}`)
93
- }
94
- />
79
+ <Route index element={<Navigate to={environmentKeys[0]} replace />} />
95
80
  </Route>
96
81
  <Route path="history" element={<DisplayFeatureHistory />} />
97
82
  </Route>
@@ -118,6 +103,7 @@ export function App() {
118
103
  </Route>
119
104
 
120
105
  <Route path="history" element={<ListHistory />} />
106
+ <Route index element={<Navigate to="features" replace />} />
121
107
  </Routes>
122
108
  </SearchIndexContext.Provider>
123
109
  )}
@@ -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/searchIndexHook";
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 [q, setQ] = React.useState("");
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 value={q} onChange={(e: any) => setQ(e.target.value)} />
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 [q, setQ] = React.useState("");
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 value={q} onChange={(e: any) => setQ(e.target.value)} />
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 [q, setQ] = React.useState("");
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 value={q} onChange={(e: any) => setQ(e.target.value)} />
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
- interface SearchInputProps {
4
- value: string;
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,8 +21,8 @@ export function SearchInput(props: SearchInputProps) {
23
21
  </div>
24
22
  <input
25
23
  type="text"
26
- value={props.value}
27
- onChange={(e) => props.onChange(e)}
24
+ value={searchQuery}
25
+ onChange={(e) => setSearchQuery(e.target.value)}
28
26
  placeholder="Type to search..."
29
27
  autoComplete="off"
30
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"
@@ -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/searchIndexHook";
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/searchIndexHook";
9
+ import { useSearchIndex } from "../hooks/useSearchIndex";
10
10
  import { isEnabledInEnvironment } from "../utils";
11
11
  import { ExpandRuleSegments } from "./ExpandRuleSegments";
12
12
  import { ExpandConditions } from "./ExpandConditions";
@@ -439,6 +439,7 @@ export function ShowFeature() {
439
439
  {
440
440
  title: "Overview",
441
441
  to: `/features/${featureKey}`,
442
+ end: true,
442
443
  },
443
444
  {
444
445
  title: "Variations",
@@ -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/searchIndexHook";
10
+ import { useSearchIndex } from "../hooks/useSearchIndex";
11
11
  import { Markdown } from "./Markdown";
12
12
 
13
13
  export function DisplaySegmentOverview() {
@@ -4,6 +4,7 @@ import { NavLink } from "react-router-dom";
4
4
  interface Tab {
5
5
  title: string;
6
6
  to: string;
7
+ end?: boolean;
7
8
  }
8
9
 
9
10
  interface TabsProps {
@@ -20,7 +21,7 @@ export function Tabs(props: TabsProps) {
20
21
  <NavLink
21
22
  key={tab.title}
22
23
  to={tab.to}
23
- end
24
+ end={Boolean(tab.end)}
24
25
  className={({ isActive }) =>
25
26
  [
26
27
  "w-1/4",
@@ -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
+ }
@@ -8,7 +8,7 @@ export interface Query {
8
8
  capture?: boolean;
9
9
  }
10
10
 
11
- export function getQueryFromString(q: string) {
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 = q.split(" ");
20
+ const parts = queryString.split(" ");
21
21
 
22
22
  for (const part of parts) {
23
23
  if (part.startsWith("tag:")) {
@@ -1 +0,0 @@
1
- export declare function useSearchIndex(): import("../contexts/SearchIndexContext").SearchIndexProps;
@@ -1,9 +0,0 @@
1
- import { useContext } from "react";
2
-
3
- import { SearchIndexContext } from "../contexts/SearchIndexContext";
4
-
5
- export function useSearchIndex() {
6
- const value = useContext(SearchIndexContext);
7
-
8
- return value;
9
- }