This is an automated email from the ASF dual-hosted git repository.

porcelli pushed a commit to branch KOGITO-8015-feature-preview
in repository 
https://gitbox.apache.org/repos/asf/incubator-kie-tools-temporary-rnd-do-not-use.git

commit 9335ac938501e5b7e980216f9fa504739afe953c
Author: Fabrizio Antonangeli <[email protected]>
AuthorDate: Tue May 23 15:25:21 2023 +0200

    KOGITO-8966: Ability to completely erase stored data on browser (#1656)
    
    Co-authored-by: Guilherme Caponetto 
<[email protected]>
---
 .../src/cookies/index.ts                           |   9 +
 .../src/home/sample/SamplesCatalog.tsx             |  14 +-
 .../src/homepage/pageTemplate/OnlineEditorPage.tsx |   6 +-
 .../src/navigation/Routes.ts                       |   1 +
 .../src/settings/routes/SettingsPageRoutes.tsx     |   4 +
 .../src/settings/storage/StorageSettings.tsx       | 199 +++++++++++++++++++++
 .../src/settings/uiNav/SettingsPageNav.tsx         |  15 +-
 .../workspace/startupBlockers/SupportedBrowsers.ts |   8 +
 8 files changed, 247 insertions(+), 9 deletions(-)

diff --git a/packages/serverless-logic-web-tools/src/cookies/index.ts 
b/packages/serverless-logic-web-tools/src/cookies/index.ts
index 73c7f62f1f..3776e20e81 100644
--- a/packages/serverless-logic-web-tools/src/cookies/index.ts
+++ b/packages/serverless-logic-web-tools/src/cookies/index.ts
@@ -32,3 +32,12 @@ export function setCookie(name: string, value: string) {
 }
 
 export const makeCookieName = (group: string, name: string) => 
`KIE-TOOLS__serverless-logic-sandbox__${group}--${name}`;
+
+/**
+ * Delete all cookies
+ */
+export const deleteAllCookies = () => {
+  document.cookie.split(";").forEach(function (c) {
+    document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new 
Date().toUTCString() + ";path=/");
+  });
+};
diff --git 
a/packages/serverless-logic-web-tools/src/home/sample/SamplesCatalog.tsx 
b/packages/serverless-logic-web-tools/src/home/sample/SamplesCatalog.tsx
index cac108bc89..76258ddfb2 100644
--- a/packages/serverless-logic-web-tools/src/home/sample/SamplesCatalog.tsx
+++ b/packages/serverless-logic-web-tools/src/home/sample/SamplesCatalog.tsx
@@ -75,7 +75,7 @@ export function SamplesCatalog() {
   const [sampleCovers, setSampleCovers] = useState<SampleCoversHashtable>({});
   const [sampleLoadingError, setSampleLoadingError] = useState("");
   const [searchFilter, setSearchFilter] = useState("");
-  const [searchParams, setSearchParams] = useState<SearchParams>({ 
searchValue: "", category: undefined });
+  const [searchParams, setSearchParams] = useState<SearchParams | 
undefined>(undefined);
   const [page, setPage] = React.useState(1);
   const [isCategoryFilterDropdownOpen, setCategoryFilterDropdownOpen] = 
useState(false);
   const history = useHistory();
@@ -121,12 +121,13 @@ export function SamplesCatalog() {
 
   const onSearch = useCallback(
     async (args: SearchParams) => {
-      if (args.searchValue === searchParams.searchValue && args.category === 
searchParams.category) {
+      if (searchParams && args.searchValue === searchParams.searchValue && 
args.category === searchParams.category) {
         return;
       }
       setSearchFilter(args.searchValue);
       setCategoryFilter(args.category);
       setSearchParams(args);
+      setPage(1);
       setSamples(await sampleDispatch.getSamples({ searchFilter: 
args.searchValue, categoryFilter: args.category }));
     },
     [sampleDispatch, setCategoryFilter, searchParams]
@@ -142,8 +143,13 @@ export function SamplesCatalog() {
   }, [categoryFilter, onSearch, searchFilter, setCategoryFilter]);
 
   useEffect(() => {
+    if (searchParams && searchFilter === searchParams.searchValue && 
categoryFilter === searchParams.category) {
+      return;
+    }
+    setSearchParams({ searchValue: searchFilter, category: categoryFilter });
+
     sampleDispatch
-      .getSamples({})
+      .getSamples({ categoryFilter })
       .then((data) => {
         const sortedSamples = data.sort(
           (a: Sample, b: Sample) => SAMPLE_PRIORITY[a.definition.category] - 
SAMPLE_PRIORITY[b.definition.category]
@@ -156,7 +162,7 @@ export function SamplesCatalog() {
       .finally(() => {
         setLoading(false);
       });
-  }, [sampleDispatch]);
+  }, [sampleDispatch, categoryFilter, searchFilter, searchParams]);
 
   useEffect(() => {
     sampleDispatch.getSampleCovers({ samples: visibleSamples, prevState: 
sampleCovers }).then(setSampleCovers);
diff --git 
a/packages/serverless-logic-web-tools/src/homepage/pageTemplate/OnlineEditorPage.tsx
 
b/packages/serverless-logic-web-tools/src/homepage/pageTemplate/OnlineEditorPage.tsx
index e37a20feb0..733cf4dd84 100644
--- 
a/packages/serverless-logic-web-tools/src/homepage/pageTemplate/OnlineEditorPage.tsx
+++ 
b/packages/serverless-logic-web-tools/src/homepage/pageTemplate/OnlineEditorPage.tsx
@@ -45,6 +45,7 @@ import {
 import { SettingsButton } from "../../settings/SettingsButton";
 import { HomePageNav } from "../uiNav/HomePageNav";
 import { APP_NAME } from "../../AppConstants";
+import { isBrowserChromiumBased } from 
"../../workspace/startupBlockers/SupportedBrowsers";
 
 export type OnlineEditorPageProps = {
   children?: React.ReactNode;
@@ -69,10 +70,7 @@ export function OnlineEditorPage(props: 
OnlineEditorPageProps) {
     useQueryParams: false,
   };
 
-  const isChromiumBased = useMemo(() => {
-    const agent = window.navigator.userAgent.toLowerCase();
-    return agent.indexOf("edg") > -1 || agent.indexOf("chrome") > -1;
-  }, []);
+  const isChromiumBased = useMemo(isBrowserChromiumBased, []);
 
   const headerToolbar = (
     <Toolbar id="toolbar" isFullHeight isStatic>
diff --git a/packages/serverless-logic-web-tools/src/navigation/Routes.ts 
b/packages/serverless-logic-web-tools/src/navigation/Routes.ts
index e2e485fdf4..fb783a26a3 100644
--- a/packages/serverless-logic-web-tools/src/navigation/Routes.ts
+++ b/packages/serverless-logic-web-tools/src/navigation/Routes.ts
@@ -148,6 +148,7 @@ export const routes = {
     service_account: new Route<{}>(() => `${SETTINGS_ROUTE}/service-account`),
     service_registry: new Route<{}>(() => 
`${SETTINGS_ROUTE}/service-registry`),
     feature_preview: new Route<{}>(() => `${SETTINGS_ROUTE}/feature-preview`),
+    storage: new Route<{}>(() => `${SETTINGS_ROUTE}/storage`),
   },
 
   static: {
diff --git 
a/packages/serverless-logic-web-tools/src/settings/routes/SettingsPageRoutes.tsx
 
b/packages/serverless-logic-web-tools/src/settings/routes/SettingsPageRoutes.tsx
index ed3da455c8..78b029faea 100644
--- 
a/packages/serverless-logic-web-tools/src/settings/routes/SettingsPageRoutes.tsx
+++ 
b/packages/serverless-logic-web-tools/src/settings/routes/SettingsPageRoutes.tsx
@@ -25,6 +25,7 @@ import { OpenShiftSettings } from 
"../openshift/OpenShiftSettings";
 import { ServiceAccountSettings } from 
"../serviceAccount/ServiceAccountSettings";
 import { ServiceRegistrySettings } from 
"../serviceRegistry/ServiceRegistrySettings";
 import { SettingsPageProps } from "../types";
+import { StorageSettings } from "../storage/StorageSettings";
 
 export function SettingsPageRoutes(props: {} & SettingsPageProps) {
   const routes = useRoutes();
@@ -52,6 +53,9 @@ export function SettingsPageRoutes(props: {} & 
SettingsPageProps) {
       <Route path={routes.settings.feature_preview.path({})}>
         <FeaturePreviewSettings />
       </Route>
+      <Route path={routes.settings.storage.path({})}>
+        <StorageSettings />
+      </Route>
       <Route>
         <Redirect to={routes.settings.github.path({})} />
       </Route>
diff --git 
a/packages/serverless-logic-web-tools/src/settings/storage/StorageSettings.tsx 
b/packages/serverless-logic-web-tools/src/settings/storage/StorageSettings.tsx
new file mode 100644
index 0000000000..ccba6c851f
--- /dev/null
+++ 
b/packages/serverless-logic-web-tools/src/settings/storage/StorageSettings.tsx
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2023 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from "react";
+import { useController } from "@kie-tools-core/react-hooks/dist/useController";
+import { Alert, AlertActionCloseButton, Button } from 
"@patternfly/react-core/dist/js";
+import { Checkbox } from "@patternfly/react-core/dist/js/components/Checkbox";
+import { Form } from "@patternfly/react-core/dist/js/components/Form";
+import { Page, PageSection } from 
"@patternfly/react-core/dist/js/components/Page";
+import { Text, TextContent, TextVariants } from 
"@patternfly/react-core/dist/js/components/Text";
+import { useCallback, useEffect, useState } from "react";
+import { Alerts, AlertsController, useAlert } from "../../alerts/Alerts";
+import { APP_NAME } from "../../AppConstants";
+import { routes } from "../../navigation/Routes";
+import { setPageTitle } from "../../PageTitle";
+import { ConfirmDeleteModal } from "../../table/ConfirmDeleteModal";
+import { SETTINGS_PAGE_SECTION_TITLE } from "../SettingsContext";
+import { deleteAllCookies } from "../../cookies";
+import { isBrowserChromiumBased } from 
"../../workspace/startupBlockers/SupportedBrowsers";
+import { useHistory } from "react-router";
+
+const PAGE_TITLE = "Storage";
+/**
+ * delete alert delay in seconds before reloading the app.
+ */
+const DELETE_ALERT_DELAY = 5;
+
+/**
+ * Delete all indexed DBs
+ */
+const deleteAllIndexedDBs = async () => {
+  Promise.all(
+    (await indexedDB.databases()).filter((db) => db.name).map(async (db) => 
indexedDB.deleteDatabase(db.name!))
+  );
+};
+
+function Timer(props: { delay: number }) {
+  const [delay, setDelay] = useState(props.delay);
+
+  useEffect(() => {
+    const timer = setInterval(() => {
+      setDelay((prevDelay) => prevDelay - 1);
+    }, 1000);
+
+    return () => {
+      clearInterval(timer);
+    };
+  }, []);
+
+  return <>{delay}</>;
+}
+
+export function StorageSettings() {
+  const [isDeleteCookiesChecked, setIsDeleteCookiesChecked] = useState(false);
+  const [isDeleteLocalStorageChecked, setIsDeleteLocalStorageChecked] = 
useState(false);
+  const [isConfirmDeleteModalOpen, setIsConfirmDeleteModalOpen] = 
useState(false);
+  const [alerts, alertsRef] = useController<AlertsController>();
+  const history = useHistory();
+
+  const toggleConfirmModal = useCallback(() => {
+    setIsConfirmDeleteModalOpen((isOpen) => !isOpen);
+  }, []);
+
+  const deleteSuccessAlert = useAlert(
+    alerts,
+    useCallback(({ close }) => {
+      setTimeout(() => {
+        window.location.href = window.location.origin + 
window.location.pathname;
+      }, DELETE_ALERT_DELAY * 1000);
+      return (
+        <Alert
+          variant="success"
+          title={
+            <>
+              Data deleted successfully. <br />
+              You will be redirected to the home page in <Timer 
delay={DELETE_ALERT_DELAY} /> seconds
+            </>
+          }
+        />
+      );
+    }, [])
+  );
+
+  const deleteErrorAlert = useAlert(
+    alerts,
+    useCallback(({ close }) => {
+      return (
+        <Alert
+          variant="danger"
+          title={`Oops, something went wrong while trying to delete the 
selected data. Please refresh the page and try again. If the problem persists, 
you can try deleting site data for this application in your browser's 
settings.`}
+          actionClose={<AlertActionCloseButton onClose={close} />}
+        />
+      );
+    }, [])
+  );
+
+  const onConfirmDeleteModalDelete = useCallback(async () => {
+    toggleConfirmModal();
+
+    try {
+      await deleteAllIndexedDBs();
+      if (isDeleteLocalStorageChecked) {
+        localStorage.clear();
+      }
+      if (isDeleteCookiesChecked) {
+        deleteAllCookies();
+      }
+      deleteSuccessAlert.show();
+    } catch (e) {
+      deleteErrorAlert.show();
+    }
+  }, [toggleConfirmModal, deleteSuccessAlert, isDeleteLocalStorageChecked, 
deleteErrorAlert, isDeleteCookiesChecked]);
+
+  useEffect(() => {
+    if (!isBrowserChromiumBased()) {
+      history.replace(routes.settings.home.path({}));
+    }
+    setPageTitle([SETTINGS_PAGE_SECTION_TITLE, PAGE_TITLE]);
+  }, [history]);
+
+  return (
+    <>
+      <Alerts ref={alertsRef} width={"500px"} />
+      <Page>
+        <PageSection variant={"light"} isWidthLimited>
+          <TextContent>
+            <Text component={TextVariants.h1}>{PAGE_TITLE}</Text>
+            <Text component={TextVariants.p}>
+              Here, you have the ability to completely erase all stored data 
in your browser.
+              <br />
+              Safely delete your cookies, modules, settings and all 
information locally stored in your browser, giving a
+              fresh start to {APP_NAME}.
+            </Text>
+          </TextContent>
+        </PageSection>
+
+        <PageSection>
+          <PageSection variant={"light"}>
+            <Form>
+              <Checkbox
+                id="delete-indexedDB"
+                label="Storage"
+                description={"Delete all databases. You will lose all your 
modules and workspaces."}
+                isChecked
+                isDisabled
+              />
+              <Alert
+                variant="warning"
+                isInline
+                title="By selecting the cookies and local storage, all your 
saved settings will be permanently erased."
+              >
+                <br />
+                <Checkbox
+                  id="delete-cookies"
+                  label="Cookies"
+                  description={"Delete all cookies."}
+                  isChecked={isDeleteCookiesChecked}
+                  onChange={setIsDeleteCookiesChecked}
+                />
+                <br />
+                <Checkbox
+                  id="delete-localStorage"
+                  label="LocalStorage"
+                  description={"Delete all localStorage information."}
+                  isChecked={isDeleteLocalStorageChecked}
+                  onChange={setIsDeleteLocalStorageChecked}
+                />
+              </Alert>
+            </Form>
+            <br />
+            <Button variant="danger" onClick={toggleConfirmModal}>
+              Delete data
+            </Button>
+          </PageSection>
+        </PageSection>
+        <ConfirmDeleteModal
+          isOpen={isConfirmDeleteModalOpen}
+          onClose={toggleConfirmModal}
+          onDelete={onConfirmDeleteModalDelete}
+          elementsTypeName="data"
+          deleteMessage="By deleting this data will permanently erase your 
stored information."
+        />
+      </Page>
+    </>
+  );
+}
diff --git 
a/packages/serverless-logic-web-tools/src/settings/uiNav/SettingsPageNav.tsx 
b/packages/serverless-logic-web-tools/src/settings/uiNav/SettingsPageNav.tsx
index 021cc91557..1673ea94b2 100644
--- a/packages/serverless-logic-web-tools/src/settings/uiNav/SettingsPageNav.tsx
+++ b/packages/serverless-logic-web-tools/src/settings/uiNav/SettingsPageNav.tsx
@@ -14,14 +14,18 @@
  * limitations under the License.
  */
 
+import React from "react";
 import { Nav, NavItem, NavList } from 
"@patternfly/react-core/dist/js/components/Nav";
-import * as React from "react";
+import { useMemo } from "react";
 import { Link } from "react-router-dom";
 import { useRoutes } from "../../navigation/Hooks";
+import { isBrowserChromiumBased } from 
"../../workspace/startupBlockers/SupportedBrowsers";
 
 export function SettingsPageNav(props: { pathname: string }) {
   const routes = useRoutes();
 
+  const isChromiumBased = useMemo(isBrowserChromiumBased, []);
+
   return (
     <>
       <div className="chr-c-app-title">Settings</div>
@@ -65,6 +69,15 @@ export function SettingsPageNav(props: { pathname: string }) 
{
           >
             <Link to={routes.settings.feature_preview.path({})}>Feature 
Preview</Link>
           </NavItem>
+          {isChromiumBased && (
+            <NavItem
+              itemId={0}
+              key={`Settings-storage-nav`}
+              isActive={props.pathname === routes.settings.storage.path({})}
+            >
+              <Link to={routes.settings.storage.path({})}>Storage</Link>
+            </NavItem>
+          )}
         </NavList>
       </Nav>
     </>
diff --git 
a/packages/serverless-logic-web-tools/src/workspace/startupBlockers/SupportedBrowsers.ts
 
b/packages/serverless-logic-web-tools/src/workspace/startupBlockers/SupportedBrowsers.ts
index d264f58180..2fadacadf8 100644
--- 
a/packages/serverless-logic-web-tools/src/workspace/startupBlockers/SupportedBrowsers.ts
+++ 
b/packages/serverless-logic-web-tools/src/workspace/startupBlockers/SupportedBrowsers.ts
@@ -52,6 +52,14 @@ export const mapSupportedVersionsToBowser = (...features: 
MinVersionForFeature[]
   );
 };
 
+/**
+ * Checks if the browser is Chromium based
+ */
+export const isBrowserChromiumBased = (): boolean => {
+  const agent = window.navigator.userAgent.toLowerCase();
+  return agent.indexOf("edg") > -1 || agent.indexOf("chrome") > -1;
+};
+
 export const SUPPORTED_BROWSERS = 
mapSupportedVersionsToBowser(SharedWebWorkersFeature, BroadcastChannelFeature);
 
 const IS_SUPPORTED = 
Bowser.getParser(window.navigator.userAgent).satisfies(SUPPORTED_BROWSERS);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to