diff --git a/client/src/components/LegacyExternalLinks.tsx b/client/src/components/LegacyExternalLinks.tsx index 1090a9d365..8fe86ff35f 100644 --- a/client/src/components/LegacyExternalLinks.tsx +++ b/client/src/components/LegacyExternalLinks.tsx @@ -89,7 +89,7 @@ function ExternalLinkText({ ); } -export type ExternalLinkRole = "button" | "link" | "text"; +type ExternalLinkRole = "button" | "link" | "text"; interface ExternalLinkProps { children?: React.ReactNode | null; diff --git a/client/src/components/clamped/ClampedParagraph.tsx b/client/src/components/clamped/ClampedParagraph.tsx deleted file mode 100644 index 0c3902d7b0..0000000000 --- a/client/src/components/clamped/ClampedParagraph.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/*! - * Copyright 2024 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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 cx from "classnames"; - -interface ClampedParagraphProps { - children: React.ReactNode; - className?: string; - lines?: number; -} -export default function ClampedParagraph({ - children, - className, - lines = 3, -}: ClampedParagraphProps) { - const style = { - display: "-webkit-box", - overflow: "hidden", - WebkitBoxOrient: "vertical" as const, - WebkitLineClamp: lines, - }; - - return ( -

- {children} -

- ); -} diff --git a/client/src/components/errors/RtkOrDataServicesError.tsx b/client/src/components/errors/RtkOrDataServicesError.tsx index eeb30ce4a2..98720bffa1 100644 --- a/client/src/components/errors/RtkOrDataServicesError.tsx +++ b/client/src/components/errors/RtkOrDataServicesError.tsx @@ -23,7 +23,7 @@ import cx from "classnames"; import { extractTextFromObject } from "../../utils/helpers/TextUtils"; import { ErrorAlert } from "../Alert"; -export interface BackendErrorResponse { +interface BackendErrorResponse { error: { code: number; message: string; diff --git a/client/src/components/loginAlert/LoginAlert.tsx b/client/src/components/loginAlert/LoginAlert.tsx index 6cfd8f165f..a0627ae308 100644 --- a/client/src/components/loginAlert/LoginAlert.tsx +++ b/client/src/components/loginAlert/LoginAlert.tsx @@ -28,7 +28,7 @@ import { Alert } from "reactstrap"; import { useLoginUrl } from "../../authentication/useLoginUrl.hook"; -export interface LoginAlertProps { +interface LoginAlertProps { logged: boolean; noWrapper?: boolean; textIntro?: string; diff --git a/client/src/components/progress/ProgressSteps.tsx b/client/src/components/progress/ProgressSteps.tsx index 39b4294617..8009988ea5 100644 --- a/client/src/components/progress/ProgressSteps.tsx +++ b/client/src/components/progress/ProgressSteps.tsx @@ -56,7 +56,7 @@ export interface StepsProgressBar { step: string; } -export interface ProgressStepsIndicatorProps { +interface ProgressStepsIndicatorProps { /** * Type of progress-bar. Indeterminate or Determinate * @default Indeterminate diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectMemberRoleSelect.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectMemberRoleSelect.tsx index a3ac38765f..c8226f23d8 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectMemberRoleSelect.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectMemberRoleSelect.tsx @@ -30,13 +30,13 @@ import Select, { import styles from "~/features/sessionsV2/components/SessionForm/Select.module.scss"; -export interface ProjectMemberRoleOption { +interface ProjectMemberRoleOption { label: string; value: string; description?: ReactNode; } -export const GROUP_MEMBER_ROLE_OPTIONS = [ +const GROUP_MEMBER_ROLE_OPTIONS = [ { value: "viewer", label: "Viewer", diff --git a/client/src/features/ProjectPageV2/utils/roleUtils.ts b/client/src/features/ProjectPageV2/utils/roleUtils.ts index 432562753b..d1ba82e8b1 100644 --- a/client/src/features/ProjectPageV2/utils/roleUtils.ts +++ b/client/src/features/ProjectPageV2/utils/roleUtils.ts @@ -22,7 +22,7 @@ import type { Role, } from "../../projectsV2/api/projectV2.api"; -export type RoleOrNone = Role | "none"; +type RoleOrNone = Role | "none"; const ROLE_MAP: Record = { owner: 30, editor: 20, diff --git a/client/src/features/admin/ConnectedServiceFormContent.tsx b/client/src/features/admin/ConnectedServiceFormContent.tsx index 809e4c8ff7..d56c9ac49c 100644 --- a/client/src/features/admin/ConnectedServiceFormContent.tsx +++ b/client/src/features/admin/ConnectedServiceFormContent.tsx @@ -23,7 +23,7 @@ import { Input, Label } from "reactstrap"; import { InfoAlert } from "~/components/Alert"; import type { ProviderForm } from "../connectedServices/api/connectedServices.types"; -export interface ConnectedServiceFormContentProps { +interface ConnectedServiceFormContentProps { control: Control; } export default function ConnectedServiceFormContent({ diff --git a/client/src/features/cloudStorage/AddStorageMountSaveCredentialsInfo.tsx b/client/src/features/cloudStorage/AddStorageMountSaveCredentialsInfo.tsx deleted file mode 100644 index 763d1dd22a..0000000000 --- a/client/src/features/cloudStorage/AddStorageMountSaveCredentialsInfo.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/*! - * Copyright 2023 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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 cx from "classnames"; -import { Control, Controller } from "react-hook-form"; -import { Label } from "reactstrap"; - -import { InfoAlert } from "../../components/Alert"; -import { - AddCloudStorageState, - AddStorageMountForm, -} from "./projectCloudStorage.types"; - -type AddStorageMountSaveCredentialsInfoProps = { - control: Control; - onFieldValueChange: (field: "saveCredentials", value: boolean) => void; - state: AddCloudStorageState; -}; - -export default function AddStorageMountSaveCredentialsInfo({ - control, - onFieldValueChange, - state, -}: AddStorageMountSaveCredentialsInfoProps) { - return ( -
- - - ( - { - field.onChange(e); - onFieldValueChange("saveCredentials", e.target.checked); - }} - value="" - checked={state.saveCredentials} - /> - )} - rules={{ required: true }} - /> - {state.saveCredentials && ( -
- -

- The credentials will be stored as secrets and only be for your - use. Other users will have to supply their credentials to use this - data connector. -

-
-
- )} -
- Check this box to save credentials as secrets, so you will not have to - provide them again when starting a session. -
-
- ); -} diff --git a/client/src/features/cloudStorage/projectCloudStorage.types.ts b/client/src/features/cloudStorage/projectCloudStorage.types.ts index 8c3bb55cb7..61562afecf 100644 --- a/client/src/features/cloudStorage/projectCloudStorage.types.ts +++ b/client/src/features/cloudStorage/projectCloudStorage.types.ts @@ -27,10 +27,7 @@ export interface CloudStorage sensitive_fields?: { name: string; help: string }[]; } -export type CloudStorageSensitiveFieldDefinition = Pick< - RCloneOption, - "name" | "help" ->; +type CloudStorageSensitiveFieldDefinition = Pick; export interface CloudStorageCredential extends CloudStorageSensitiveFieldDefinition { @@ -105,10 +102,3 @@ export type CloudStorageDetails = { }; export type AuxiliaryCommandStatus = "failure" | "none" | "success" | "trying"; - -export interface AddStorageMountForm { - name: string; - mountPoint: string; - readOnly: boolean; - saveCredentials: boolean; -} diff --git a/client/src/features/cloudStorage/projectCloudStorage.utils.ts b/client/src/features/cloudStorage/projectCloudStorage.utils.ts index af48d4fcbb..6827a7d932 100644 --- a/client/src/features/cloudStorage/projectCloudStorage.utils.ts +++ b/client/src/features/cloudStorage/projectCloudStorage.utils.ts @@ -46,7 +46,7 @@ import type { const LAST_POSITION = 1000; -export interface CloudStorageOptions extends RCloneOption { +interface CloudStorageOptions extends RCloneOption { requiredCredential: boolean; } diff --git a/client/src/features/connectedServices/connectedServices.constants.ts b/client/src/features/connectedServices/connectedServices.constants.ts index d760913dfc..3e6e6c0c82 100644 --- a/client/src/features/connectedServices/connectedServices.constants.ts +++ b/client/src/features/connectedServices/connectedServices.constants.ts @@ -16,7 +16,6 @@ * limitations under the License. */ -export const INTERNAL_GITLAB_PROVIDER_ID = "INTERNAL_GITLAB"; export const SEARCH_PARAM_PROVIDER = "targetProvider"; export const SEARCH_PARAM_ACTION_REQUIRED = "actionRequired"; export const SEARCH_PARAM_SOURCE = "source"; diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx index 64041af528..e22443fa5c 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalBody.tsx @@ -211,9 +211,7 @@ type DataConnectorMountFormFields = | "slug" | "visibility"; -export function DataConnectorMount({ - dataConnector, -}: AddOrEditDataConnectorProps) { +function DataConnectorMount({ dataConnector }: AddOrEditDataConnectorProps) { const dispatch = useAppDispatch(); const { cloudStorageState, flatDataConnector, schemata } = useAppSelector( (state) => state.dataConnectorFormSlice diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalResult.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalResult.tsx index 05efc968b6..cacf28e3c9 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalResult.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorModal/DataConnectorModalResult.tsx @@ -19,8 +19,6 @@ import { SuccessAlert } from "../../../../components/Alert"; import useAppSelector from "../../../../utils/customHooks/useAppSelector.hook"; -export type AuxiliaryCommandStatus = "failure" | "none" | "success" | "trying"; - interface DataConnectorModalResultProps { alreadyExisted: boolean; } diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx index 354127bc6a..1e03b51dee 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorModal/index.tsx @@ -253,7 +253,7 @@ interface DataConnectorModalHeaderProps { dataConnectorId: string | null; initialStep?: number; } -export function DataConnectorModalHeader({ +function DataConnectorModalHeader({ dataConnectorId, initialStep, }: DataConnectorModalHeaderProps) { diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorView.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorView.tsx index ff5c7c7c92..529240f364 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorView.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorView.tsx @@ -18,6 +18,7 @@ import { skipToken } from "@reduxjs/toolkit/query"; import cx from "classnames"; +import { capitalize } from "lodash-es"; import { useCallback, useMemo, useRef, useState } from "react"; import { Cloud, @@ -52,7 +53,6 @@ import { WarnAlert } from "../../../components/Alert"; import { Clipboard } from "../../../components/clipboard/Clipboard"; import { Loader } from "../../../components/Loader"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; -import { toCapitalized } from "../../../utils/helpers/HelperFunctions"; import { CredentialMoreInfo } from "../../cloudStorage/CloudStorageItem"; import { CLOUD_STORAGE_SENSITIVE_FIELD_TOKEN, @@ -376,7 +376,7 @@ function DataConnectorViewConfiguration({ {scope !== "global" && nonRequiredCredentialConfigurationKeys.map((key) => { const title = - key == "provider" && hasAccessMode ? "Mode" : toCapitalized(key); + key == "provider" && hasAccessMode ? "Mode" : capitalize(key); const value = storageDefinition.configuration[key]?.toString() ?? ""; return ( diff --git a/client/src/features/dataConnectorsV2/components/DataConnectorsBoxListDisplay.tsx b/client/src/features/dataConnectorsV2/components/DataConnectorsBoxListDisplay.tsx index bd1b4eb90a..7a829c6306 100644 --- a/client/src/features/dataConnectorsV2/components/DataConnectorsBoxListDisplay.tsx +++ b/client/src/features/dataConnectorsV2/components/DataConnectorsBoxListDisplay.tsx @@ -243,7 +243,7 @@ interface DataConnectorNotVisibleToAllUsersBadgeProps { warning?: string; } -export function DataConnectorNotVisibleToAllUsersBadge({ +function DataConnectorNotVisibleToAllUsersBadge({ className, }: DataConnectorNotVisibleToAllUsersBadgeProps) { const ref = useRef(null); @@ -275,7 +275,7 @@ interface IntegrationBadgeProps { dataConnector: DataConnector; } -export function IntegrationBadge({ dataConnector }: IntegrationBadgeProps) { +function IntegrationBadge({ dataConnector }: IntegrationBadgeProps) { const providerKind = useMemo( () => CLOUD_STORAGE_INTEGRATION_KIND_MAP[dataConnector.storage.storage_type], diff --git a/client/src/features/dataConnectorsV2/components/useDataConnectorProjects.hook.ts b/client/src/features/dataConnectorsV2/components/useDataConnectorProjects.hook.ts index ec68e3c81f..6f0bdbc3a5 100644 --- a/client/src/features/dataConnectorsV2/components/useDataConnectorProjects.hook.ts +++ b/client/src/features/dataConnectorsV2/components/useDataConnectorProjects.hook.ts @@ -20,15 +20,9 @@ import { skipToken } from "@reduxjs/toolkit/query"; import { useMemo } from "react"; import { useGetProjectsByProjectIdsQuery } from "../../projectsV2/api/projectV2.enhanced-api"; -import type { SessionStartDataConnectorConfiguration } from "../../sessionsV2/startSessionOptionsV2.types"; import type { DataConnectorRead } from "../api/data-connectors.api"; import { useGetDataConnectorsByDataConnectorIdProjectLinksQuery } from "../api/data-connectors.enhanced-api"; -export interface DataConnectorConfiguration - extends Omit { - dataConnector: DataConnectorRead; -} - interface UseDataSourceConfigurationArgs { dataConnector: DataConnectorRead | undefined; } diff --git a/client/src/features/dataConnectorsV2/dataConnectors.types.ts b/client/src/features/dataConnectorsV2/dataConnectors.types.ts index d3d6f39a21..ad0874ddc7 100644 --- a/client/src/features/dataConnectorsV2/dataConnectors.types.ts +++ b/client/src/features/dataConnectorsV2/dataConnectors.types.ts @@ -16,10 +16,4 @@ * limitations under the License. */ -import type { DataConnector } from "./api/data-connectors.api"; - export type DataConnectorScope = "global" | "namespace" | "project"; - -export type DataConnectorWithScope = DataConnector & { - scope: DataConnectorScope; -}; diff --git a/client/src/features/dataConnectorsV2/state/dataConnectors.slice.ts b/client/src/features/dataConnectorsV2/state/dataConnectors.slice.ts index fad4c89117..17c1a4f79e 100644 --- a/client/src/features/dataConnectorsV2/state/dataConnectors.slice.ts +++ b/client/src/features/dataConnectorsV2/state/dataConnectors.slice.ts @@ -26,13 +26,13 @@ import { import { EMPTY_CLOUD_STORAGE_STATE } from "../../cloudStorage/projectCloudStorage.constants"; import { AddCloudStorageState, + AuxiliaryCommandStatus, CloudStorageSchema, } from "../../cloudStorage/projectCloudStorage.types"; import { EMPTY_DATA_CONNECTOR_FLAT, type DataConnectorFlat, } from "../components/dataConnector.utils"; -import type { AuxiliaryCommandStatus } from "../components/DataConnectorModal/DataConnectorModalResult"; interface BackendResult { isSuccess: boolean | undefined; diff --git a/client/src/features/display/display.types.ts b/client/src/features/display/display.types.ts index 57a4c61d0c..5bfccabac8 100644 --- a/client/src/features/display/display.types.ts +++ b/client/src/features/display/display.types.ts @@ -16,31 +16,13 @@ * limitations under the License. */ -interface ProjectConfig { - projectPath: string; - gitUrl: string; - branch: string; -} - -interface SessionConfig { - targetServer: string; -} - -interface FaviconSet { - ico: string; - png_32x: string; - png_16x: string; - svg: string; -} - export type FaviconStatus = | "general" | "running" | "waiting" | "error" | "pause"; -interface Display { + +export interface Display { favicon: FaviconStatus; } - -export type { Display, FaviconSet, ProjectConfig, SessionConfig }; diff --git a/client/src/features/display/index.ts b/client/src/features/display/index.ts deleted file mode 100644 index 1e52564ffa..0000000000 --- a/client/src/features/display/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Display, ProjectConfig } from "./display.types"; -import { displaySlice, reset, resetFavicon, setFavicon } from "./displaySlice"; - -export { displaySlice, reset, resetFavicon, setFavicon }; -export type { Display, ProjectConfig }; diff --git a/client/src/features/groupsV2/fields/GroupMemberRoleSelect.tsx b/client/src/features/groupsV2/fields/GroupMemberRoleSelect.tsx index 14949100f0..b682609d0f 100644 --- a/client/src/features/groupsV2/fields/GroupMemberRoleSelect.tsx +++ b/client/src/features/groupsV2/fields/GroupMemberRoleSelect.tsx @@ -30,13 +30,13 @@ import Select, { import styles from "~/features/sessionsV2/components/SessionForm/Select.module.scss"; -export interface GroupMemberRoleOption { +interface GroupMemberRoleOption { label: string; value: string; description?: ReactNode; } -export const GROUP_MEMBER_ROLE_OPTIONS = [ +const GROUP_MEMBER_ROLE_OPTIONS = [ { value: "viewer", label: "Viewer", diff --git a/client/src/features/groupsV2/new/CreateGroupButton.tsx b/client/src/features/groupsV2/new/CreateGroupButton.tsx index 232819fff3..3cdbddfdaa 100644 --- a/client/src/features/groupsV2/new/CreateGroupButton.tsx +++ b/client/src/features/groupsV2/new/CreateGroupButton.tsx @@ -22,7 +22,7 @@ import { Button } from "reactstrap"; import useLocationHash from "../../../utils/customHooks/useLocationHash.hook"; import { GROUP_CREATION_HASH } from "./createGroup.constants"; -export interface CreateGroupButtonProps { +interface CreateGroupButtonProps { children?: React.ReactNode; className?: string; color?: string; diff --git a/client/src/features/projectsV2/fields/UserSelector.tsx b/client/src/features/projectsV2/fields/UserSelector.tsx index 91260396a8..35abd01913 100644 --- a/client/src/features/projectsV2/fields/UserSelector.tsx +++ b/client/src/features/projectsV2/fields/UserSelector.tsx @@ -158,7 +158,7 @@ interface UserSelectorProps { onSetQuery: (q: string) => void; query: string; } -export function UserSelector({ +function UserSelector({ currentUser, isFetchingMore, users, diff --git a/client/src/features/projectsV2/new/CreateProjectV2Button.tsx b/client/src/features/projectsV2/new/CreateProjectV2Button.tsx index 11c8f87f43..1dc0ddb77a 100644 --- a/client/src/features/projectsV2/new/CreateProjectV2Button.tsx +++ b/client/src/features/projectsV2/new/CreateProjectV2Button.tsx @@ -22,7 +22,7 @@ import { Button } from "reactstrap"; import useLocationHash from "../../../utils/customHooks/useLocationHash.hook"; import { PROJECT_CREATION_HASH } from "./createProjectV2.constants"; -export interface CreateProjectV2ButtonProps { +interface CreateProjectV2ButtonProps { children?: React.ReactNode; className?: string; color?: string; diff --git a/client/src/features/projectsV2/show/VisibilityIconV2.tsx b/client/src/features/projectsV2/show/VisibilityIconV2.tsx index 3d7f0457b9..611906a6d6 100644 --- a/client/src/features/projectsV2/show/VisibilityIconV2.tsx +++ b/client/src/features/projectsV2/show/VisibilityIconV2.tsx @@ -22,7 +22,7 @@ import { useRef } from "react"; import { Globe, Lock } from "react-bootstrap-icons"; import { UncontrolledTooltip } from "reactstrap"; -export interface VisibilityIconV2Props { +interface VisibilityIconV2Props { visibility: "public" | "private"; className?: string; } diff --git a/client/src/features/recentUserActivity/RecentUserActivityApi.ts b/client/src/features/recentUserActivity/RecentUserActivityApi.ts deleted file mode 100644 index fc13b1e031..0000000000 --- a/client/src/features/recentUserActivity/RecentUserActivityApi.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*! - * Copyright 2022 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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 { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; - -interface KgLastSearchResult { - queries?: string[]; - error?: string; -} -export const TOTAL_QUERIES = 6; - -export const recentUserActivityApi = createApi({ - reducerPath: "recentUserActivityApi", - baseQuery: fetchBaseQuery({ baseUrl: "/ui-server/api/" }), - endpoints: (builder) => ({ - searchLastQueries: builder.query({ - query: (numberWords) => `last-searches/${numberWords}`, - transformResponse: (response: KgLastSearchResult) => { - let queries: string[] = []; - if (response?.queries?.length) { - queries = response?.queries - .map((query) => query.replace(/\*/g, "").trim()) - .filter((query) => query.length > 0) - .slice(0, 5); - } - response.queries = queries; - return response; - }, - }), - }), -}); - -// Export hooks for usage in function components, which are -// auto-generated based on the defined endpoints -export const { useSearchLastQueriesQuery } = recentUserActivityApi; diff --git a/client/src/features/searchV2/contextSearch.constants.tsx b/client/src/features/searchV2/contextSearch.constants.tsx index 2ce37bafd3..1d1df5aa18 100644 --- a/client/src/features/searchV2/contextSearch.constants.tsx +++ b/client/src/features/searchV2/contextSearch.constants.tsx @@ -31,54 +31,14 @@ import { Tag, } from "react-bootstrap-icons"; -import { - EnumFilter, - Filter, - NumberFilter, - StringFilter, -} from "./contextSearch.types"; +import { EnumFilter } from "./contextSearch.types"; import { DATE_AFTER_LEEWAY, DATE_BEFORE_LEEWAY } from "./searchV2.constants"; export const VALUE_SEPARATOR_AND = "+"; -export const VALUE_SEPARATOR_OR = ","; +const VALUE_SEPARATOR_OR = ","; export const DEFAULT_ELEMENTS_LIMIT_IN_FILTERS = 5; -export const FILTER_PAGE: NumberFilter & - Required> = { - name: "page", - label: "Page", - type: "number", - defaultValue: 1, - minValue: 1, -}; - -export const FILTER_PER_PAGE: NumberFilter & - Required> = { - name: "perPage", - label: "Per page", - type: "number", - defaultValue: 10, - minValue: 1, - maxValue: 100, -}; - -export const FILTER_QUERY: StringFilter = { - name: "q", - label: "Query", - type: "string", - defaultValue: "", -}; - -export const NAMESPACE_FILTER: StringFilter = { - name: "namespace", - label: "Namespace", - type: "string", - defaultValue: "", -}; - -export const DEFAULT_INCLUDE_COUNTS = true; - export const FILTER_CONTENT: EnumFilter = { name: "type", label: ( @@ -318,47 +278,3 @@ export const FILTER_DATE: EnumFilter = { }); }, }; - -export const COMMON_FILTERS: Filter[] = [ - FILTER_CONTENT, - FILTER_PAGE, - FILTER_PER_PAGE, - FILTER_QUERY, -]; - -export const PROJECT_FILTERS: Filter[] = [ - FILTER_MEMBER, - FILTER_KEYWORD, - FILTER_VISIBILITY, - FILTER_MY_ROLE, - FILTER_DATE, -]; - -export const DATACONNECTORS_FILTERS: Filter[] = [ - FILTER_KEYWORD, - FILTER_VISIBILITY, - FILTER_DATE, -]; - -export const SELECTABLE_FILTERS: Filter[] = [ - FILTER_CONTENT, - FILTER_MEMBER, - FILTER_KEYWORD, - FILTER_VISIBILITY, - FILTER_MY_ROLE, - FILTER_DATE, -]; - -export const ALL_FILTERS: Filter[] = [ - FILTER_PAGE, - FILTER_PER_PAGE, - FILTER_QUERY, - FILTER_CONTENT, - FILTER_MEMBER, - FILTER_KEYWORD, - FILTER_VISIBILITY, - FILTER_MY_ROLE, - FILTER_DATE, -]; - -export const SEARCH_DEBOUNCE_SECONDS = 1; diff --git a/client/src/features/searchV2/contextSearch.types.ts b/client/src/features/searchV2/contextSearch.types.ts index f3560527c8..df721d5a1f 100644 --- a/client/src/features/searchV2/contextSearch.types.ts +++ b/client/src/features/searchV2/contextSearch.types.ts @@ -44,7 +44,7 @@ interface BaseFilter { validFor?: SearchEntity["type"][]; } -export interface StringFilter extends BaseFilter { +interface StringFilter extends BaseFilter { defaultValue?: string; type: "string"; } @@ -57,7 +57,7 @@ export interface EnumFilter extends BaseFilter { valueSeparator?: string; } -export interface NumberFilter extends BaseFilter { +interface NumberFilter extends BaseFilter { defaultValue?: number; maxValue?: number; minValue?: number; @@ -70,26 +70,3 @@ export type Filter = Extract< Filter_ & { type: T }, Filter_ >; - -type ValueType = T extends "number" - ? number - : T extends "string" | "enum" - ? string - : string | number; - -type FilterWithValue_ = - | { - filter: StringFilter | EnumFilter; - value: string; - } - | { filter: NumberFilter; value: number }; - -export type FilterWithValue = - Extract< - FilterWithValue_ & { filter: Filter; value: ValueType }, - FilterWithValue_ - >; - -export type SearchQueryFilters = { - [key: Filter["name"]]: FilterWithValue | undefined; -}; diff --git a/client/src/features/searchV2/contextSearch.utils.ts b/client/src/features/searchV2/contextSearch.utils.ts new file mode 100644 index 0000000000..3feecc1ca2 --- /dev/null +++ b/client/src/features/searchV2/contextSearch.utils.ts @@ -0,0 +1,43 @@ +/*! + * Copyright 2025 - Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * 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 { DATE_FILTER_CUSTOM_SEPARATOR } from "./contextSearch.constants"; + +export function parseCustomDateFilter(value: string): { + afterDate: string; + beforeDate: string; +} { + const parts = value.split(DATE_FILTER_CUSTOM_SEPARATOR); + let afterDate = ""; + let beforeDate = ""; + for (const part of parts) { + if (part.startsWith(">")) afterDate = part.slice(1); + else if (part.startsWith("<")) beforeDate = part.slice(1); + } + return { afterDate, beforeDate }; +} + +export function buildCustomDateFilterValue( + afterDate: string, + beforeDate: string +): string { + const parts: string[] = []; + if (afterDate) parts.push(`>${afterDate}`); + if (beforeDate) parts.push(`<${beforeDate}`); + return parts.join(DATE_FILTER_CUSTOM_SEPARATOR); +} diff --git a/client/src/features/searchV2/contextSearch.utils.tsx b/client/src/features/searchV2/contextSearch.utils.tsx deleted file mode 100644 index a33f8d1924..0000000000 --- a/client/src/features/searchV2/contextSearch.utils.tsx +++ /dev/null @@ -1,287 +0,0 @@ -/*! - * Copyright 2025 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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 { ReactNode } from "react"; - -import type { SearchQuery } from "~/features/searchV2/api/searchV2Api.api"; -import { - ALL_FILTERS, - COMMON_FILTERS, - DATACONNECTORS_FILTERS, - DATE_FILTER_CUSTOM_SEPARATOR, - DEFAULT_INCLUDE_COUNTS, - FILTER_CONTENT, - FILTER_DATE, - FILTER_MY_ROLE, - FILTER_PAGE, - FILTER_PER_PAGE, - FILTER_QUERY, - FILTER_VISIBILITY, - NAMESPACE_FILTER, - PROJECT_FILTERS, - SELECTABLE_FILTERS, - VALUE_SEPARATOR_AND, - VALUE_SEPARATOR_OR, -} from "./contextSearch.constants"; -import type { - Filter, - FilterWithValue, - SearchQueryFilters, -} from "./contextSearch.types"; -import { KEY_VALUE_SEPARATOR, TERM_SEPARATOR } from "./searchV2.constants"; -import type { - CreationDateFilter, - ParseSearchQueryResult, -} from "./searchV2.types"; - -/** @deprecated No longer used - the Redux slice handles filter state. */ -export function getSearchQueryFilters( - searchParams: URLSearchParams, - filters: Filter[] = COMMON_FILTERS -): SearchQueryFilters { - return filters.reduce((acc, filter) => { - const raw = searchParams.get(filter.name); - if (raw !== null) { - if (filter.type === "number") { - try { - const parsed = parseInt(raw, 10); - acc[filter.name] = { - filter, - value: parsed, - }; - return acc; - } catch { - // Do not add the filter - return acc; - } - } - - // The filter is string or enum here - acc[filter.name] = { filter, value: raw }; - } - return acc; - }, {}); -} - -/** @deprecated No longer used - useSearchSync handles defaults. */ -export function getSearchQueryMissingFilters( - searchParams: URLSearchParams, - filters: Filter[] = COMMON_FILTERS -): SearchQueryFilters { - const existing = getSearchQueryFilters(searchParams, filters); - return filters.reduce((acc, filter) => { - if (existing[filter.name] == null && filter.defaultValue != null) { - acc[filter.name] = { - filter, - value: filter.defaultValue, - } as FilterWithValue; - } - return acc; - }, {}); -} - -/** @deprecated Use `buildApiQuery` from searchV2.utils.ts instead. */ -export function generateQueryParams( - searchParams: URLSearchParams, - groupSlug?: string, - ignoredParams: string[] = [] -): SearchQuery { - const commonFilters = getSearchQueryFilters(searchParams, COMMON_FILTERS); - const queryFilters = getSearchQueryFilters(searchParams, [ - FILTER_CONTENT, - ...(searchParams.get(FILTER_CONTENT.name) === "DataConnector" - ? DATACONNECTORS_FILTERS - : PROJECT_FILTERS), - ]); - const queryFiltersForGroup = groupSlug - ? { - ...queryFilters, - namespace: { - filter: NAMESPACE_FILTER, - value: groupSlug, - }, - } - : queryFilters; - const mustQuoteFilters = ALL_FILTERS.filter((filter) => filter.mustQuote).map( - (filter) => filter.name - ); - - const queryFiltersProcessed = Object.entries(queryFiltersForGroup).reduce< - string[] - >((acc, [key, filterWithValue]) => { - if (!ignoredParams.includes(key) && filterWithValue?.value != null) { - // Use custom query term builder if available (e.g., date filters) - if (filterWithValue.filter.buildQueryTerms) { - return [ - ...acc, - ...filterWithValue.filter.buildQueryTerms(key, filterWithValue.value), - ]; - } - - const { value } = filterWithValue; - const quote = mustQuoteFilters.includes(key) ? '"' : ""; - const values = - typeof value === "string" && value.includes(VALUE_SEPARATOR_AND) - ? value.split(VALUE_SEPARATOR_AND) - : [value]; - for (const value of values) { - acc = [...acc, `${key}${KEY_VALUE_SEPARATOR}${quote}${value}${quote}`]; - } - } - return acc; - }, []); - - const query = [ - ...queryFiltersProcessed, - (commonFilters[FILTER_QUERY.name] as FilterWithValue<"string">).value, - ].join(TERM_SEPARATOR); - return { - q: query.trim(), - page: - (commonFilters[FILTER_PAGE.name] as FilterWithValue<"number">)?.value ?? - FILTER_PAGE.defaultValue, - per_page: - (commonFilters[FILTER_PER_PAGE.name] as FilterWithValue<"number">) - ?.value ?? FILTER_PER_PAGE.defaultValue, - include_counts: DEFAULT_INCLUDE_COUNTS, - }; -} - -/** @deprecated Filter summary is now built from Redux state in SearchResultRecap. */ -export function getQueryHumanReadable( - searchParams: URLSearchParams, - filters: Filter[] = SELECTABLE_FILTERS -): ReactNode { - const filterNamesToLabel = filters.reduce>( - (acc, filter) => { - acc[filter.name] = filter.label; - return acc; - }, - {} - ); - const queryFilters = getSearchQueryFilters(searchParams, filters); - const validFilters = Object.entries(queryFilters).filter( - ([, filterWithValue]) => filterWithValue?.value != null - ); - const queryParts = validFilters.map(([key, filterWithValue], index) => ( - - {filterNamesToLabel[key] ?? key}: {filterWithValue?.value} - {index < validFilters.length - 1 && <> + } - - )); - - return <>{queryParts}; -} - -/** @deprecated Use `parsedResultToSliceParams` from searchV2.utils.ts instead. */ -export function mapParsedQueryToSearchParams( - result: ParseSearchQueryResult, - currentParams?: URLSearchParams -): URLSearchParams { - const params = new URLSearchParams(currentParams); - - // Map type filter (lowercase → capitalized to match context-search values) - if (result.filters.type.values.length > 0) { - const typeValue = capitalizeEntityType(result.filters.type.values[0]); - params.set(FILTER_CONTENT.name, typeValue); - } - - // Map role filter - if (result.filters.role.values.length > 0) { - params.set( - FILTER_MY_ROLE.name, - result.filters.role.values.join(VALUE_SEPARATOR_OR) - ); - } - - // Map visibility filter - if (result.filters.visibility.values.length > 0) { - params.set(FILTER_VISIBILITY.name, result.filters.visibility.values[0]); - } - - // Map date filter - const dateValue = mapDateFilterToParam(result.dateFilters.created); - if (dateValue) { - params.set(FILTER_DATE.name, dateValue); - } - - // Set free text query (without filter terms) - params.set(FILTER_QUERY.name, result.searchBarQuery); - - // Reset page to first - params.set(FILTER_PAGE.name, FILTER_PAGE.defaultValue.toString()); - - return params; -} - -function capitalizeEntityType(type: string): string { - const matched = FILTER_CONTENT.allowedValues.find( - (v) => v.value.toLowerCase() === type.toLowerCase() - ); - return matched?.value ?? type; -} - -function mapDateFilterToParam(filter: CreationDateFilter): string | null { - const parts: string[] = []; - - if (filter.value.after != null) { - const afterStr = - typeof filter.value.after === "string" - ? filter.value.after - : filter.value.after.date.toISODate(); - if (afterStr) { - parts.push(`>${afterStr}`); - } - } - - if (filter.value.before != null) { - const beforeStr = - typeof filter.value.before === "string" - ? filter.value.before - : filter.value.before.date.toISODate(); - if (beforeStr) { - parts.push(`<${beforeStr}`); - } - } - - return parts.length > 0 ? parts.join(DATE_FILTER_CUSTOM_SEPARATOR) : null; -} - -export function parseCustomDateFilter(value: string): { - afterDate: string; - beforeDate: string; -} { - const parts = value.split(DATE_FILTER_CUSTOM_SEPARATOR); - let afterDate = ""; - let beforeDate = ""; - for (const part of parts) { - if (part.startsWith(">")) afterDate = part.slice(1); - else if (part.startsWith("<")) beforeDate = part.slice(1); - } - return { afterDate, beforeDate }; -} - -export function buildCustomDateFilterValue( - afterDate: string, - beforeDate: string -): string { - const parts: string[] = []; - if (afterDate) parts.push(`>${afterDate}`); - if (beforeDate) parts.push(`<${beforeDate}`); - return parts.join(DATE_FILTER_CUSTOM_SEPARATOR); -} diff --git a/client/src/features/searchV2/hooks/useContextSearch.hook.ts b/client/src/features/searchV2/hooks/useContextSearch.hook.ts deleted file mode 100644 index 03eadfe5f8..0000000000 --- a/client/src/features/searchV2/hooks/useContextSearch.hook.ts +++ /dev/null @@ -1,45 +0,0 @@ -/*! - * Copyright 2025 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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. - */ - -/** - * @deprecated Use `useGetSearchQueryQuery` with `selectSearchApiQuery` from - * the Redux slice instead. This hook is superseded by `useSearchSync.hook.ts`. - */ - -import { useMemo } from "react"; -import { useSearchParams } from "react-router"; - -import { useGetSearchQueryQuery } from "~/features/searchV2/api/searchV2Api.api"; -import { useNamespaceContext } from "~/features/searchV2/hooks/useNamespaceContext.hook"; -import { SEARCH_DEBOUNCE_SECONDS } from "../contextSearch.constants"; -import { generateQueryParams } from "../contextSearch.utils"; - -export function useContextSearch(ignoredParams?: string[]) { - const [searchParams] = useSearchParams(); - const { namespace } = useNamespaceContext(); - - const params = useMemo( - () => generateQueryParams(searchParams, namespace, ignoredParams), - [namespace, ignoredParams, searchParams] - ); - - return useGetSearchQueryQuery( - { params }, - { refetchOnMountOrArgChange: SEARCH_DEBOUNCE_SECONDS } - ); -} diff --git a/client/src/features/searchV2/hooks/useReduxFilterParam.hook.ts b/client/src/features/searchV2/hooks/useReduxFilterParam.hook.ts index 5b9a3f8097..d7c026be15 100644 --- a/client/src/features/searchV2/hooks/useReduxFilterParam.hook.ts +++ b/client/src/features/searchV2/hooks/useReduxFilterParam.hook.ts @@ -29,9 +29,6 @@ import { setVisibility, } from "../searchV2.slice"; -/** - * Redux-based replacement for useSearchFilterParam. - */ export function useReduxFilterParam(filterName: string) { const dispatch = useAppDispatch(); const state = useAppSelector(({ searchV2 }) => searchV2); diff --git a/client/src/features/searchV2/hooks/useSearch.hook.ts b/client/src/features/searchV2/hooks/useSearch.hook.ts deleted file mode 100644 index 182cb9c8da..0000000000 --- a/client/src/features/searchV2/hooks/useSearch.hook.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*! - * Copyright 2024 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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 - */ - -/** - * @deprecated Use `useSearchSync` instead. This hook is superseded by - * `useSearchSync.hook.ts` which handles bidirectional URL-Redux sync - * for all search components. - */ -export default function useSearch() { - // no-op: this hook is deprecated. - // Use useSearchSync from ./useSearchSync.hook.ts instead. -} diff --git a/client/src/features/searchV2/hooks/useSearchFilterParam.hook.ts b/client/src/features/searchV2/hooks/useSearchFilterParam.hook.ts deleted file mode 100644 index dc236bf626..0000000000 --- a/client/src/features/searchV2/hooks/useSearchFilterParam.hook.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*! - * Copyright 2026 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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. - */ - -/** - * @deprecated Use `useReduxFilterParam` instead. - */ - -import { useCallback } from "react"; -import { useSearchParams } from "react-router"; - -import { FILTER_PAGE } from "../contextSearch.constants"; - -export function useSearchFilterParam(filterName: string) { - const [searchParams, setSearchParams] = useSearchParams(); - const currentValue = searchParams.get(filterName) ?? ""; - - const updateParam = useCallback( - (newValue: string) => { - const params = new URLSearchParams(searchParams); - if (newValue) { - params.set(filterName, newValue); - } else { - params.delete(filterName); - } - const pageDefaultValue = FILTER_PAGE.defaultValue.toString(); - if (params.get(FILTER_PAGE.name) !== pageDefaultValue) { - params.set(FILTER_PAGE.name, pageDefaultValue); - } - setSearchParams(params); - }, - [filterName, searchParams, setSearchParams] - ); - - return { currentValue, updateParam }; -} diff --git a/client/src/features/searchV2/searchV2.constants.ts b/client/src/features/searchV2/searchV2.constants.ts index 037a482e81..4b213e0f89 100644 --- a/client/src/features/searchV2/searchV2.constants.ts +++ b/client/src/features/searchV2/searchV2.constants.ts @@ -78,7 +78,7 @@ export const VISIBILITY_FILTER_ALLOWED_VALUES: SearchEntityVisibility[] = [ // Creation date filter constants -export const DEFAULT_DATE_FILTER_VALUE: SearchDateFilter["value"] = {}; +const DEFAULT_DATE_FILTER_VALUE: SearchDateFilter["value"] = {}; export const DATE_AFTER_LEEWAY = "-1d"; export const DATE_BEFORE_LEEWAY = "+1d"; diff --git a/client/src/features/searchV2/searchV2.types.ts b/client/src/features/searchV2/searchV2.types.ts index 4232163430..840265527f 100644 --- a/client/src/features/searchV2/searchV2.types.ts +++ b/client/src/features/searchV2/searchV2.types.ts @@ -120,18 +120,6 @@ export type VisibilityFilter = SearchFilterBase< export type SearchEntityVisibility = Lowercase; -export interface SortBy { - key: "sort"; - value: SortByValue; -} - -export type SortByValue = - | "score-desc" - | "created-desc" - | "created-asc" - | "name-asc" - | "name-desc"; - // Types related to parsing a search query export interface ParseSearchQueryResult { canonicalQuery: string; diff --git a/client/src/features/searchV2/searchV2.utils.ts b/client/src/features/searchV2/searchV2.utils.ts index f7fe7c59ff..55f04937e2 100644 --- a/client/src/features/searchV2/searchV2.utils.ts +++ b/client/src/features/searchV2/searchV2.utils.ts @@ -280,7 +280,7 @@ function parseTerm(term: string): InterpretedTerm { }; } -export function valuesAsSet(values: T[]): Set { +function valuesAsSet(values: T[]): Set { return values.reduce((set, value) => set.add(value), new Set()); } diff --git a/client/src/features/sessionsV2/SessionShowPage/ShowSessionPage.tsx b/client/src/features/sessionsV2/SessionShowPage/ShowSessionPage.tsx index 1bceb2df61..8351f9d86f 100644 --- a/client/src/features/sessionsV2/SessionShowPage/ShowSessionPage.tsx +++ b/client/src/features/sessionsV2/SessionShowPage/ShowSessionPage.tsx @@ -52,7 +52,7 @@ import { TimeCaption } from "../../../components/TimeCaption"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; import useAppDispatch from "../../../utils/customHooks/useAppDispatch.hook"; import useWindowSize from "../../../utils/helpers/UseWindowsSize"; -import { resetFavicon, setFavicon } from "../../display"; +import { resetFavicon, setFavicon } from "../../display/displaySlice"; import type { Project } from "../../projectsV2/api/projectV2.api"; import { useGetNamespacesByNamespaceProjectsAndSlugQuery } from "../../projectsV2/api/projectV2.enhanced-api"; import { diff --git a/client/src/features/sessionsV2/SessionStartPage.tsx b/client/src/features/sessionsV2/SessionStartPage.tsx index e786ae434e..8add3c1876 100644 --- a/client/src/features/sessionsV2/SessionStartPage.tsx +++ b/client/src/features/sessionsV2/SessionStartPage.tsx @@ -43,7 +43,7 @@ import { } from "../cloudStorage/projectCloudStorage.utils"; import { usePatchDataConnectorsByDataConnectorIdSecretsMutation } from "../dataConnectorsV2/api/data-connectors.enhanced-api"; import type { DataConnectorConfiguration } from "../dataConnectorsV2/components/useDataConnectorConfiguration.hook"; -import { resetFavicon, setFavicon } from "../display"; +import { resetFavicon, setFavicon } from "../display/displaySlice"; import type { SessionSecretSlotWithSecret } from "../ProjectPageV2/ProjectPageContent/SessionSecrets/sessionSecrets.types"; import type { Project } from "../projectsV2/api/projectV2.api"; import { useGetNamespacesByNamespaceProjectsAndSlugQuery } from "../projectsV2/api/projectV2.enhanced-api"; diff --git a/client/src/features/sessionsV2/components/SessionModals/UpdateSessionLauncherModal.tsx b/client/src/features/sessionsV2/components/SessionModals/UpdateSessionLauncherModal.tsx index da913e9eaf..52a390d870 100644 --- a/client/src/features/sessionsV2/components/SessionModals/UpdateSessionLauncherModal.tsx +++ b/client/src/features/sessionsV2/components/SessionModals/UpdateSessionLauncherModal.tsx @@ -18,7 +18,7 @@ import cx from "classnames"; import { useCallback, useEffect, useMemo } from "react"; -import { CheckLg, Pencil, XLg } from "react-bootstrap-icons"; +import { CheckLg, XLg } from "react-bootstrap-icons"; import { useForm } from "react-hook-form"; import { Button, Form, ModalBody, ModalFooter, ModalHeader } from "reactstrap"; @@ -36,9 +36,7 @@ import { getLauncherDefaultValues, } from "../../session.utils"; import { SessionLauncherForm } from "../../sessionsV2.types"; -import EditLauncherFormContent, { - EditLauncherFormMetadata, -} from "../SessionForm/EditLauncherFormContent"; +import EditLauncherFormContent from "../SessionForm/EditLauncherFormContent"; import { EnvironmentIcon } from "../SessionForm/LauncherEnvironmentIcon"; export interface UpdateSessionLauncherModalProps { @@ -167,124 +165,6 @@ export default function UpdateSessionLauncherEnvironmentModal({ ); } -export function UpdateSessionLauncherMetadataModal({ - isOpen, - launcher, - toggle, -}: UpdateSessionLauncherModalProps) { - const { data: environments } = useGetSessionEnvironmentsQuery({}); - const [updateSessionLauncher, result] = useUpdateSessionLauncherMutation(); - const defaultValues = useMemo( - () => getLauncherDefaultValues(launcher), - [launcher] - ); - - const { - control, - formState: { errors, isDirty, touchedFields }, - handleSubmit, - reset, - setValue, - watch, - } = useForm({ - defaultValues, - }); - const onSubmit = useCallback( - (data: SessionLauncherForm) => { - const { description, name } = data; - const environment = getFormattedEnvironmentValuesForEdit(data); - if (environment.success && environment.data) - updateSessionLauncher({ - launcherId: launcher.id, - sessionLauncherPatch: { - name, - description: description?.trim() || undefined, - environment: environment.data, - }, - }); - }, - [launcher.id, updateSessionLauncher] - ); - - useEffect(() => { - if (environments == null) { - return; - } - if (environments.length == 0) { - setValue("environmentSelect", "custom + image"); - } - }, [environments, setValue]); - - useEffect(() => { - if (!isOpen) { - reset(); - result.reset(); - } - }, [isOpen, reset, result]); - - useEffect(() => { - reset(defaultValues); - }, [launcher, reset, defaultValues]); - - return ( - - - - Edit session launcher {launcher.name} - - - {result.isSuccess ? ( - - ) : ( -
- {result.error && } - - - )} -
- - - {!result.isSuccess && ( - - )} - -
- ); -} - const ConfirmationUpdate = () => { return (
diff --git a/client/src/features/sessionsV2/components/SessionStatus/SessionStatus.tsx b/client/src/features/sessionsV2/components/SessionStatus/SessionStatus.tsx index 27960d85c8..b0d4e39e78 100644 --- a/client/src/features/sessionsV2/components/SessionStatus/SessionStatus.tsx +++ b/client/src/features/sessionsV2/components/SessionStatus/SessionStatus.tsx @@ -273,7 +273,7 @@ export function SessionStatusV2Description({ interface StatusExtraDetailsV2Props { status: SessionStatus; } -export function SessionListRowStatusExtraDetailsV2({ +function SessionListRowStatusExtraDetailsV2({ status, }: StatusExtraDetailsV2Props) { const ref = useRef(null); diff --git a/client/src/features/sessionsV2/components/SessionsList.tsx b/client/src/features/sessionsV2/components/SessionsList.tsx index 1949e9394e..b29b3c3921 100644 --- a/client/src/features/sessionsV2/components/SessionsList.tsx +++ b/client/src/features/sessionsV2/components/SessionsList.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -export interface SessionLauncherResources { +interface SessionLauncherResources { poolName?: string; name?: string; cpu?: number; @@ -25,7 +25,7 @@ export interface SessionLauncherResources { storage?: number; } -export interface SessionResources { +interface SessionResources { requests?: { cpu?: number; memory?: string; storage?: string }; usage?: { cpu?: number; memory?: string; storage?: string }; } diff --git a/client/src/features/sessionsV2/sessionsV2.types.ts b/client/src/features/sessionsV2/sessionsV2.types.ts index b8f49aec63..c129bc6803 100644 --- a/client/src/features/sessionsV2/sessionsV2.types.ts +++ b/client/src/features/sessionsV2/sessionsV2.types.ts @@ -18,66 +18,18 @@ import type { ReactNode } from "react"; -import type { CloudStorageDetailsOptions } from "../cloudStorage/projectCloudStorage.types"; import type { ResourceClassWithId } from "./api/computeResources.api"; import type { BuildParametersPost, DefaultUrl, EnvironmentGid, EnvironmentId, - EnvironmentKind, EnvironmentPort, EnvironmentPost, EnvironmentUid, - SessionLauncherEnvironmentParams, SessionLauncherPost, } from "./api/sessionLaunchersV2.api"; -export type SessionLauncherEnvironment = { - id?: string; - name: string; - description?: string; - container_image: string; - default_url?: string; - uid?: number; - gid?: number; - working_directory?: string; - mount_directory?: string; - port?: number; - environment_kind: EnvironmentKind; - command?: string[]; - args?: string[]; -}; - -export interface GetProjectSessionLauncherParams { - id: string; -} -export interface GetProjectSessionLaunchersParams { - projectId: string; -} - -export type AddSessionLauncherParams = { - description?: string; - name: string; - project_id: string; - resource_class_id?: number; - disk_storage?: number; - environment: SessionLauncherEnvironmentParams; -}; - -export interface UpdateSessionLauncherParams { - launcherId: string; - description?: string; - name?: string; - resource_class_id?: number; - disk_storage?: number | null; - environment?: SessionLauncherEnvironmentParams; -} - -export interface DeleteSessionLauncherParams { - launcherId: string; -} - export interface SessionLauncherForm extends Pick< SessionLauncherPost, @@ -148,43 +100,6 @@ export interface SessionV2 { resource_class_id: number; } -export interface SessionCloudStorageV2 { - configuration: CloudStorageDetailsOptions; - readonly: boolean; - source_path: string; - storage_id: string; - target_path: string; -} - -export interface LaunchSessionParams { - launcher_id: string; - disk_storage?: number; - cloudstorage?: SessionCloudStorageV2[]; - resource_class_id?: number; -} - -export interface PatchSessionParams { - session_id: string; - state?: Extract<"running" | "hibernated", SessionStatus["state"]>; - resource_class_id?: number; -} - -export interface GetLogsParams { - session_id: string; - max_lines: number; -} - -export interface StopSessionParams { - session_id: string; -} -export interface SessionImageParams { - image_url: string; -} - -export interface DockerImage { - error?: unknown; -} - export interface BuilderSelectorOption { label: string; value: T; diff --git a/client/src/features/versions/versions.types.ts b/client/src/features/versions/versions.types.ts index e3179f768d..cca57d6de7 100644 --- a/client/src/features/versions/versions.types.ts +++ b/client/src/features/versions/versions.types.ts @@ -16,8 +16,6 @@ * limitations under the License. */ -import { CoreErrorContent } from "../../utils/types/coreService.types"; - export interface BaseVersionResponse { name: string; versions: BaseVersion[]; @@ -37,6 +35,15 @@ export interface CoreVersionDetails extends BaseVersionResponse { versions: CoreComponent[]; } +interface CoreErrorContent { + code: number; + devMessage: string; + devReference?: string; + sentry?: string; + userMessage: string; + userReference?: string; +} + export interface CoreVersionResponse { error?: CoreErrorContent; result?: CoreVersionDetails; diff --git a/client/src/notebooks/Notebooks.state.js b/client/src/notebooks/Notebooks.state.js deleted file mode 100644 index 77267f3a16..0000000000 --- a/client/src/notebooks/Notebooks.state.js +++ /dev/null @@ -1,407 +0,0 @@ -/*! - * Copyright 2019 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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. - */ - -/** - * renku-ui - * - * Notebooks.state.js - * Notebooks controller code. - */ - -import { parseINIString } from "../utils/helpers/HelperFunctions"; - -const POLLING_INTERVAL = 3000; -const RENKU_INI_SECTION_LEGACY = `renku "interactive"`; -const RENKU_INI_SECTION = "interactive"; - -const CI_TYPES = { - anonymous: "anonymous", - pinned: "pinned", - logged: "logged", - owner: "owner", -}; - -const CI_STAGES = { - starting: "starting", - image: "image", - pipelines: "pipelines", - jobs: "jobs", - looping: "looping", -}; - -const CI_STATUSES = { - wrong: "wrong", - success: "success", - running: "running", - failure: "failure", -}; - -const VALID_SETTINGS = [ - "image", - "cpu_request", - "memory_request", - "disk_request", - "gpu_request", -]; - -const SESSIONS_PREFIX = "interactive."; - -const ExpectedAnnotations = { - domain: "renku.io", - "renku.io": { - required: [ - "branch", - "commit-sha", - "default_image_used", - "namespace", - "gitlabProjectId", - "projectName", - "repository", - "resourceClassId", - "hibernation", - "hibernationBranch", - "hibernationCommitSha", - "hibernationDate", - "hibernationDirty", - "hibernationSynchronized", - "hibernatedSecondsThreshold", - // Renku 2.0 annotations - "renkuVersion", - "projectId", - "launcherId", - ], - default: { - branch: "unknown", - "commit-sha": "00000000", - default_image_used: false, - namespace: "unknown", - gitlabProjectId: 0, - projectName: "unknown", - repository: "https://none", - resourceClassId: "0", - hibernation: {}, - hibernationBranch: "", - hibernationCommitSha: "", - hibernationDate: "", - hibernationDirty: false, - hibernationSynchronized: false, - hibernatedSecondsThreshold: "0", - }, - }, -}; - -const LOG_ERROR_KEY = "__error"; - -const NotebooksHelper = { - /** - * Add missing annotations from the notebook servers - * - * @param {object} annotations - * @param {string} [domain] - */ - cleanAnnotations: (annotations, domain = ExpectedAnnotations.domain) => { - let cleaned = {}; - const prefix = `${domain}/`; - ExpectedAnnotations[domain].required.forEach((annotation) => { - if ( - annotations[prefix + annotation] !== undefined || - annotations[annotation] !== undefined - ) { - let value = annotations[prefix + annotation] ?? annotations[annotation]; - // convert text boolean where a boolean is expected - if ( - annotation === "default_image_used" || - annotation === "hibernationDirty" || - annotation === "hibernationSynchronized" - ) { - const origValue = - annotations[prefix + annotation] ?? annotations[annotation]; - if ( - (origValue && - typeof origValue === "string" && - origValue.toLowerCase() === "true") || - origValue === true - ) - value = true; - else value = false; - } - // convert text json - if (annotation === "hibernation") { - try { - value = JSON.parse(value); - } catch (error) { - if (error instanceof SyntaxError) { - value = {}; - } else { - throw error; - } - } - } - cleaned[annotation] = value; - } else { - cleaned[annotation] = ExpectedAnnotations[domain].default[annotation]; - } - }); - - return { ...cleaned }; - }, - - /** - * Get options and default values from notebooks and project options - * - * @param {object} notebooksOptions - notebook options as return by the "GET server_options" API. - * @param {object} projectOptions - project options as returned by the "GET config.show" API. - * @param {string} [projectPrefix] - project option key prefix used by config to identify session options. - */ - getProjectDefault: ( - notebooksOptions, - projectOptions, - projectPrefix = SESSIONS_PREFIX - ) => { - let returnObject = {}; - if (!notebooksOptions || !projectOptions) return returnObject; - - // Conversion helper - const convert = (value) => { - // return boolean - if (value === true || value === false) return value; - - // convert stringy boolean - if (value && value.toLowerCase() === "true") return true; - else if (value && value.toLowerCase() === "false") return false; - // convert stringy number - else if (!isNaN(value)) return parseFloat(value); - - return value; - }; - - // Define options - const sortedOptions = Object.keys(notebooksOptions).sort( - (a, b) => - parseInt(notebooksOptions[a].order) - - parseInt(notebooksOptions[b].order) - ); - let unknownOptions = []; - - // Get RenkuLab defaults - let globalDefaults = [...sortedOptions].reduce((defaults, option) => { - if ("default" in notebooksOptions[option]) - defaults[option] = notebooksOptions[option].default; - return defaults; - }, {}); - - // Overwrite renku defaults - if ( - projectOptions && - projectOptions.default && - Object.keys(projectOptions.default) - ) { - for (const [key, value] of Object.entries(projectOptions.default)) { - if (key.startsWith(projectPrefix)) { - const option = key.substring(projectPrefix.length); - if (!sortedOptions.includes(option)) unknownOptions.push(option); - globalDefaults[option] = convert(value); - } - } - } - - // Get project defaults - let projectDefaults = []; - if ( - projectOptions && - projectOptions.config && - Object.keys(projectOptions.config) - ) { - for (const [key, value] of Object.entries(projectOptions.config)) { - if (key.startsWith(projectPrefix)) { - const option = key.substring(projectPrefix.length); - if (!sortedOptions.includes(option)) unknownOptions.push(option); - projectDefaults[option] = convert(value); - } - } - } - - return { - defaults: { - global: globalDefaults, - project: projectDefaults, - }, - options: { - known: sortedOptions, - unknown: unknownOptions, - }, - }; - }, - - /** - * Parse project options raw data from the .ini file to a JS object - * - * @param {string} data - raw file content - */ - // TODO: use the getProjectDefault function after switching to the "GET config.show" API - parseProjectOptions: (data) => { - let projectOptions = {}; - - // try to parse the data - let parsedData; - try { - parsedData = parseINIString(data); - } catch (error) { - parsedData = {}; - } - // check single props when the environment sections is available - let parsedOptions = parsedData[RENKU_INI_SECTION]; - // check also the previous section name for compatibility reasons - if (!parsedOptions) parsedOptions = parsedData[RENKU_INI_SECTION_LEGACY]; - if (parsedOptions) { - Object.keys(parsedOptions).forEach((parsedOption) => { - // treat "defaultUrl" as "default_url" to allow name consistency in the the .ini file - let option = parsedOption; - if (parsedOption === "defaultUrl") option = "default_url"; - - // convert boolean and numbers - let value = parsedOptions[parsedOption]; - if (value && value.toLowerCase() === "true") - projectOptions[option] = true; - else if (value && value.toLowerCase() === "false") - projectOptions[option] = false; - else if (!isNaN(value)) projectOptions[option] = parseFloat(value); - else projectOptions[option] = value; - }); - } - - return projectOptions; - }, - - /** - * Check if a project option can be applied according to the deployment global options - * - * @param {object} globalOptions - global options object - * @param {string} currentOption - current project option name - * @param {string} currentValue - current project option value - */ - checkOptionValidity: (globalOptions, currentOption, currentValue) => { - // default_url is a special case and any string will fit - if (currentOption === "default_url") { - if (typeof currentValue === "string") return true; - return false; - } - - const globalOption = globalOptions[currentOption]; - // the project option must be part of the global options - if (!globalOption) return false; - - // non-enum options require only type-check - if (globalOption.type !== "enum") { - if (globalOption.type === "boolean") { - if (typeof currentValue === "boolean") return true; - return false; - } else if (globalOption.type === "float" || globalOption.type === "int") { - if (typeof currentValue === "number") return true; - return false; - } - return false; - } - - // enum options must have a value valid for the corresponding global options - if ( - globalOption.options && - globalOption.options.length && - globalOption.options.includes(currentValue) - ) - return true; - - return false; - }, - - /** - * Check if a project setting is valid - * - * @param {string} name - setting name - * @param {string} value - setting value - */ - checkSettingValidity: (name, value) => { - if (!name || typeof name !== "string" || !name.length) return false; - if (!VALID_SETTINGS.includes(name)) return false; - if (!value || typeof value !== "string" || !value.length) return false; - - return true; - }, - - /** - * Check the CI status for the currently selected commit. - * - * @param {object} ci - ci object as stored in redux. - * @returns {object} ci status, including `stage` ("starting" --> "pipelines" --> "jobs" --> "image"), - * `error` (null or ), ongoing (), finished (). - * The props should be check in order, so that a null stage means nothing has been checked, no matter - * what error contains. In the same way, when we get an error it does not matter what available says. - */ - checkCiStatus: (ci) => { - const returnObject = { - stage: null, - error: null, - ongoing: null, - available: false, - }; - - if (ci.stage == null) return returnObject; - if (ci.stage === CI_STAGES.starting) { - returnObject.stage = CI_STAGES.starting; - returnObject.ongoing = true; - return returnObject; - } - - returnObject.stage = ci.stage; - const currentCi = ci[ci.stage]; - - if (currentCi.error != null) { - returnObject.error = currentCi.error; - returnObject.ongoing = false; - } else if (!currentCi.fetched) { - returnObject.ongoing = true; - } else if (ci.stage === CI_STAGES.image || ci.stage === CI_STAGES.looping) { - // Can't be available if we are checking pipelines in another stage - returnObject.available = currentCi.available ? true : false; - } - // We don't use `fetching` because some stages keep polling - returnObject.ongoing = currentCi.fetched ? false : true; - - return returnObject; - }, - - getCiJobStatus: (job) => { - if (job && job["id"]) { - if (job.status === "success") return CI_STATUSES.success; - if (["running", "pending", "stopping"].includes(job.status)) - return CI_STATUSES.running; - if (["failed", "canceled"].includes(job.status)) - return CI_STATUSES.failure; - } - return CI_STATUSES.wrong; - }, - - ciStatuses: CI_STATUSES, - ciStages: CI_STAGES, - ciTypes: CI_TYPES, - pollingInterval: POLLING_INTERVAL, - sessionConfigPrefix: SESSIONS_PREFIX, - validSettings: VALID_SETTINGS, -}; - -export { LOG_ERROR_KEY, NotebooksHelper, ExpectedAnnotations }; diff --git a/client/src/notebooks/notebooks.types.ts b/client/src/notebooks/notebooks.types.ts deleted file mode 100644 index 9cb7c7c366..0000000000 --- a/client/src/notebooks/notebooks.types.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface NotebookAnnotations { - branch: string; - "commit-sha": string; - default_image_used: boolean; - namespace: string; - gitlabProjectId: number; - projectName: string; - repository: string; - resourceClassId: string; - - hibernation: Record; - hibernationBranch: string; - hibernationCommitSha: string; - hibernationDate: string; - hibernationDirty: boolean; - hibernationSynchronized: boolean; - hibernatedSecondsThreshold: string; - - // Annotations for Renku 2.0 - renkuVersion?: string; - projectId?: string; - launcherId?: string; - - [key: string]: unknown; -} diff --git a/client/src/notifications/Notifications.constants.ts b/client/src/notifications/Notifications.constants.ts index da127caaea..ee0b9b4268 100644 --- a/client/src/notifications/Notifications.constants.ts +++ b/client/src/notifications/Notifications.constants.ts @@ -16,23 +16,9 @@ * limitations under the License. */ -export enum NOTIFICATION_LEVELS { - INFO = "info", - SUCCESS = "success", - WARNING = "warning", - ERROR = "error", -} - export enum NOTIFICATION_TOPICS { AUTHENTICATION = "Authentication", SESSION_START = "Session", PROJECT_DELETED = "Project deleted", PROJECT_UPDATED = "Project updated", } - -export const NotificationTypes = { - TOAST: "toast", - DROPDOWN: "dropdown", - COMPLETE: "complete", - CUSTOM: "custom", -}; diff --git a/client/src/store/store.ts b/client/src/store/store.ts index 17ec96fad4..d7e4bddf33 100644 --- a/client/src/store/store.ts +++ b/client/src/store/store.ts @@ -31,7 +31,6 @@ import { notificationsEmptyApi as notificationsApi } from "~/features/notificati import { platformEmptyApi as platformApi } from "~/features/platform/api/platform-empty.api"; import { statuspageEmptyApi as statuspageApi } from "~/features/platform/statuspage-api/statuspage-empty.api"; import { projectV2Api } from "~/features/projectsV2/api/projectV2.enhanced-api"; -import { recentUserActivityApi } from "~/features/recentUserActivity/RecentUserActivityApi"; import { repositoriesApi } from "~/features/repositories/api/repositories.api"; import { searchV2EmptyApi as searchV2Api } from "~/features/searchV2/api/searchV2-empty.api"; import { searchV2Slice } from "~/features/searchV2/searchV2.slice"; @@ -66,7 +65,6 @@ export const store = configureStore({ [platformApi.reducerPath]: platformApi.reducer, [projectCloudStorageApi.reducerPath]: projectCloudStorageApi.reducer, [projectV2Api.reducerPath]: projectV2Api.reducer, - [recentUserActivityApi.reducerPath]: recentUserActivityApi.reducer, [repositoriesApi.reducerPath]: repositoriesApi.reducer, [searchV2Api.reducerPath]: searchV2Api.reducer, [sessionLaunchersV2Api.reducerPath]: sessionLaunchersV2Api.reducer, @@ -91,7 +89,6 @@ export const store = configureStore({ .concat(platformApi.middleware) .concat(projectCloudStorageApi.middleware) .concat(projectV2Api.middleware) - .concat(recentUserActivityApi.middleware) .concat(repositoriesApi.middleware) .concat(searchV2Api.middleware) .concat(sessionLaunchersV2Api.middleware) diff --git a/client/src/storybook/bootstrap/utils.tsx b/client/src/storybook/bootstrap/utils.tsx index d47446bc2f..18cd0fea7f 100644 --- a/client/src/storybook/bootstrap/utils.tsx +++ b/client/src/storybook/bootstrap/utils.tsx @@ -51,7 +51,7 @@ export function resolveCssVar(value: string) { .trim(); } -export function getRootFontSize(): number { +function getRootFontSize(): number { return parseFloat(getComputedStyle(document.documentElement).fontSize); } diff --git a/client/src/utils/Utils.test.jsx b/client/src/utils/Utils.test.jsx index 4a075e4750..3dc50aa743 100644 --- a/client/src/utils/Utils.test.jsx +++ b/client/src/utils/Utils.test.jsx @@ -27,110 +27,10 @@ import { describe, expect, it } from "vitest"; import { convertUnicodeToAscii, - formatBytes, - getEntityImageUrl, - parseINIString, - refreshIfNecessary, slugFromTitle, - splitAutosavedBranches, } from "./helpers/HelperFunctions"; import { verifyTitleCharacters } from "./helpers/verifyTitleCharacters.utils"; -describe("Ini file parser", () => { - it("valid code", () => { - // simple variable - let content = "my_prop = abc"; - let parsedCode = parseINIString(content); - // variables are parsed as key-value pairs and return in an object - expect(Object.keys(parsedCode).length).toBe(1); - expect(Object.keys(parsedCode)).toContain("my_prop"); - expect(parsedCode.my_prop).toBe("abc"); - - // multiple variables - content = ` - my_prop_1 = 1 - my_prop_2 = true`; - parsedCode = parseINIString(content); - expect(Object.keys(parsedCode).length).toBe(2); - expect(Object.keys(parsedCode)).toContain("my_prop_1"); - expect(Object.keys(parsedCode)).toContain("my_prop_2"); - // note that values are not automatically converted - expect(parsedCode.my_prop_1).toBe("1"); - expect(parsedCode.my_prop_1).not.toBe(1); - expect(parsedCode.my_prop_2).toBe("true"); - expect(parsedCode.my_prop_2).not.toBe(true); - - // sections - content = ` - [sub] - my_prop = cde`; - parsedCode = parseINIString(content); - // variables in sections sections are parsed as sub-objects - expect(Object.keys(parsedCode).length).toBe(1); - expect(Object.keys(parsedCode)).toContain("sub"); - expect(Object.keys(parsedCode.sub).length).toBe(1); - expect(Object.keys(parsedCode.sub)).toContain("my_prop"); - expect(parsedCode.sub.my_prop).toBe("cde"); - }); - it("invalid code", () => { - // random string - const content = "this is a random string"; - const parsedCode = parseINIString(content); - // any valid string always returns an object - expect(typeof parsedCode).toBe("object"); - expect(Object.keys(parsedCode).length).toBe(0); - }); - it("partially valid code", () => { - // sections - const content = ` - valid_prop_1 = abc - random_text - 123 - valid_prop_2 = def`; - const parsedCode = parseINIString(content); - // the function try to parse everything and leaves out simple errors - expect(Object.keys(parsedCode).length).toBe(2); - expect(Object.keys(parsedCode)).toContain("valid_prop_1"); - expect(Object.keys(parsedCode)).toContain("valid_prop_2"); - expect(Object.keys(parsedCode)).not.toContain("random_text"); - expect(Object.keys(parsedCode)).not.toContain("123"); - expect(parsedCode.valid_prop_1).toBe("abc"); - expect(parsedCode.valid_prop_2).toBe("def"); - }); - it("throwing code", () => { - // any non-string will throw an exception - const invalid_contents = [true, 12345, null, undefined, [], {}]; - invalid_contents.forEach((content) => { - // eslint-disable-next-line - expect(() => { - parseINIString(content); - }).toThrow(); - }); - }); -}); - -describe("branch functions", () => { - const branches = [ - { name: "master" }, - { name: "renku/autosave/myUser/master/1234567/890acbd" }, - ]; - - it("function splitAutosavedBranches", () => { - const splitBranches = splitAutosavedBranches(branches); - expect(splitBranches.standard.length).toEqual(1); - expect(splitBranches.autosaved.length).toEqual(1); - const [username, branch, commit, finalCommit] = branches[1].name - .replace("renku/autosave/", "") - .split("/"); - expect(splitBranches.autosaved[0].autosave.username).toEqual(username); - expect(splitBranches.autosaved[0].autosave.branch).toEqual(branch); - expect(splitBranches.autosaved[0].autosave.commit).toEqual(commit); - expect(splitBranches.autosaved[0].autosave.finalCommit).toEqual( - finalCommit - ); - }); -}); - describe("title related functions", () => { // convertUnicodeToAscii it("function convertUnicodeToAscii - valid strings", () => { @@ -192,75 +92,3 @@ describe("title related functions", () => { expect(verifyTitleCharacters("yeah 🚀")).toBeFalsy(); }); }); - -describe("function getEntityImageUrl", () => { - it("get image url", () => { - const validImages = [ - { - _links: [{ href: "url-example" }, { href: "url-example2" }], - }, - ]; - expect(getEntityImageUrl(validImages)).toEqual("url-example"); - let invalidImages = []; - expect(getEntityImageUrl([])).toEqual(undefined); - invalidImages = [{ _links: [] }]; - expect(getEntityImageUrl(invalidImages)).toEqual(undefined); - invalidImages = [ - { - _links: "is not array", - }, - ]; - expect(getEntityImageUrl(invalidImages)).toEqual(undefined); - invalidImages = [ - { - _links: [{ noHref: "url-example" }], - }, - ]; - expect(getEntityImageUrl(invalidImages)).toEqual(undefined); - }); -}); - -describe("function formatBytes", () => { - it("formatBytes", () => { - expect(formatBytes(1551859712)).toEqual("1.45 GB"); - expect(formatBytes(1551859712, 1)).toEqual("1.4 GB"); - expect(formatBytes(5000)).toEqual("4.88 KB"); - expect(formatBytes(1100000, 0)).toEqual("1 MB"); - expect(formatBytes(1000000)).toEqual("976.56 KB"); - expect(formatBytes(-1000000)).toEqual("-976.56 KB"); - expect(formatBytes("aaa")).toEqual("NaN"); - }); -}); - -describe("function refreshIfNecessary", () => { - it("formatBytes", () => { - const fakeFunction = () => { - return true; - }; - - // fetch on falsy data - expect(refreshIfNecessary(null, null, fakeFunction)).toBe(true); - - // Do not fetch when it's already fetching - expect(refreshIfNecessary(true, null, fakeFunction)).toBeUndefined(); - expect(refreshIfNecessary(false, null, fakeFunction)).toBe(true); - - // Fetch only when outdated - const fiveSecondsAgo = new Date(+new Date() - 5000); - const fifteenSecondsAgo = new Date(+new Date() - 15000); - expect(refreshIfNecessary(false, fifteenSecondsAgo, fakeFunction)).toBe( - true - ); - expect( - refreshIfNecessary(true, fiveSecondsAgo, fakeFunction) - ).toBeUndefined(); - - // Tolerance - expect( - refreshIfNecessary(true, fiveSecondsAgo, fakeFunction, 50) - ).toBeUndefined(); - expect(refreshIfNecessary(false, fiveSecondsAgo, fakeFunction, 2)).toBe( - true - ); - }); -}); diff --git a/client/src/utils/constants/Docs.js b/client/src/utils/constants/Docs.js index c0e69aa853..1b6d6db83a 100644 --- a/client/src/utils/constants/Docs.js +++ b/client/src/utils/constants/Docs.js @@ -8,29 +8,10 @@ const READ_THE_DOCS_ROOT = "https://renku.readthedocs.io/en/stable"; const Docs = { - READ_THE_DOCS_DEVELOPER: `${READ_THE_DOCS_ROOT}/developer`, - READ_THE_DOCS_HOW_TO_GUIDES: `${READ_THE_DOCS_ROOT}/how-to-guides`, - READ_THE_DOCS_INTRODUCTION: `${READ_THE_DOCS_ROOT}/introduction`, - READ_THE_DOCS_REFERENCE: `${READ_THE_DOCS_ROOT}/reference`, - READ_THE_DOCS_RENKU_PYTHON: `${READ_THE_DOCS_ROOT}/renku-python`, READ_THE_DOCS_ROOT: READ_THE_DOCS_ROOT, READ_THE_DOCS_TUTORIALS: `${READ_THE_DOCS_ROOT}/tutorials`, // eslint-disable-next-line READ_THE_DOCS_TUTORIALS_STARTING: `${READ_THE_DOCS_ROOT}/tutorials/01_firststeps.html`, - READ_THE_DOCS_WHY_RENKU: `${READ_THE_DOCS_ROOT}/introduction/why.html`, - - rtdHowToGuide(subPage) { - return `${Docs.READ_THE_DOCS_HOW_TO_GUIDES}/${subPage}`; - }, - rtdPythonReferencePage(subPage) { - return `${Docs.READ_THE_DOCS_RENKU_PYTHON}/docs/reference/${subPage}`; - }, - rtdReferencePage(subPage) { - return `${Docs.READ_THE_DOCS_REFERENCE}/${subPage}`; - }, - rtdTopicGuide(subPage) { - return `${Docs.READ_THE_DOCS_ROOT}/topic-guides/${subPage}`; - }, }; const Links = { @@ -39,7 +20,6 @@ const Links = { GITHUB: "https://github.com/SwissDataScienceCenter/renku", HOMEPAGE: "https://datascience.ch", MASTODON: "https://fosstodon.org/@renku", - MEDIUM: "https://medium.com/the-renku-blog", YOUTUBE: "https://www.youtube.com/channel/UCMF2tBtWU1sKWvtPl_HpI4A", SDSC: "https://www.datascience.ch/", ETH: "https://ethrat.ch/en/", @@ -56,15 +36,4 @@ const Links = { "https://www.notion.so/renku/2540df2efafc80c5ac36c6ecf6a375a0?v=2540df2efafc80b1ae2b000cee3e5a3c#2540df2efafc80c5ac36c6ecf6a375a0", }; -const GitlabLinks = { - PROJECT_VISIBILITY: "https://docs.gitlab.com/ee/user/public_access.html", -}; - -const REKNU_PYTHON_READ_THE_DOCS_ROOT = - "https://renku-python.readthedocs.io/en/stable"; - -const RenkuPythonDocs = { - READ_THE_DOCS_ROOT: REKNU_PYTHON_READ_THE_DOCS_ROOT, -}; - -export { Docs, GitlabLinks, Links, RenkuPythonDocs }; +export { Docs, Links }; diff --git a/client/src/utils/constants/NewDocs.ts b/client/src/utils/constants/NewDocs.ts index 832c584080..223bc25a65 100644 --- a/client/src/utils/constants/NewDocs.ts +++ b/client/src/utils/constants/NewDocs.ts @@ -28,7 +28,7 @@ const DEFAULT_NEW_DOC_LINK_ARGS: NewDocLinkArgs = { version: "latest", }; -export const NEW_DOCS_ROOT = "https://docs.renkulab.io/"; +const NEW_DOCS_ROOT = "https://docs.renkulab.io/"; function newDocsBase({ language, version }: NewDocLinkArgs): string { return `${NEW_DOCS_ROOT}${language}/${version}/`; @@ -56,10 +56,6 @@ export const NEW_DOCS_DATA_CONNECTORS_FROM_REPO = newDocsLinkPage( "docs/users/data/guides/connect-data/connect-data-from-data-repositories" )(DEFAULT_NEW_DOC_LINK_ARGS); -export const NEW_DOCS_MIGRATION_INFO = newDocsLinkPage( - "docs/users/migrate-v1-v2/migrate-renku-1-project-to-renku-2" -)(DEFAULT_NEW_DOC_LINK_ARGS); - export const NEW_DOCS_HOW_TO_USE_OWN_DOCKER_IMAGE = newDocsLinkPage( "docs/users/sessions/guides/environments/use-your-own-docker-image-for-renku-session" )(DEFAULT_NEW_DOC_LINK_ARGS); diff --git a/client/src/utils/customHooks/useLocationHash.hook.ts b/client/src/utils/customHooks/useLocationHash.hook.ts index 41a1c2eb5c..f0c98ddefc 100644 --- a/client/src/utils/customHooks/useLocationHash.hook.ts +++ b/client/src/utils/customHooks/useLocationHash.hook.ts @@ -46,7 +46,7 @@ export default function useLocationHash(): [string, SetLocationHash] { return [hash, setLocationHash]; } -export type SetLocationHash = ( +type SetLocationHash = ( next?: string | ((prev: string) => string), options?: NavigateOptions ) => void; diff --git a/client/src/utils/helpers/ApiErrors.ts b/client/src/utils/helpers/ApiErrors.ts index cea14392fe..dd6a43638c 100644 --- a/client/src/utils/helpers/ApiErrors.ts +++ b/client/src/utils/helpers/ApiErrors.ts @@ -18,18 +18,6 @@ import { FetchBaseQueryError } from "@reduxjs/toolkit/query/react"; -interface RenkuError { - code: number; - message: string; -} - -interface RenkuErrorResponse { - data: { - error: RenkuError; - }; - status: number; -} - export const isFetchBaseQueryError = ( error: FetchBaseQueryError | unknown ): error is FetchBaseQueryError => { @@ -40,30 +28,3 @@ export const isFetchBaseQueryError = ( } return false; }; - -// See also CoreErrorHelpers.js -export function isRenkuError(error: unknown): error is RenkuError { - if (error == null) return false; - if (typeof error !== "object") return false; - if (!("code" in error)) return false; - if (!("message" in error)) return false; - const errorCode = (error as RenkuError).code; - if (errorCode >= 1000 || errorCode < 0) return true; - return false; -} - -export function isRenkuErrorResponse( - response: unknown -): response is RenkuErrorResponse { - if (!isFetchBaseQueryError(response)) return false; - - if ( - !("data" in response) || - typeof response.data !== "object" || - response.data == null - ) - return false; - if (!("error" in response.data) || typeof response.data.error !== "object") - return false; - return isRenkuError(response.data.error); -} diff --git a/client/src/utils/helpers/HelperFunctions.js b/client/src/utils/helpers/HelperFunctions.js index c5f6bf8546..ee5aa65980 100644 --- a/client/src/utils/helpers/HelperFunctions.js +++ b/client/src/utils/helpers/HelperFunctions.js @@ -110,148 +110,6 @@ function slugFromTitle( return slug; } -function splitAutosavedBranches(branches) { - const autosaved = branches - .filter((branch) => branch.name.startsWith(AUTOSAVED_PREFIX)) - .map((branch) => { - let autosave = {}; - const autosaveData = branch.name.replace(AUTOSAVED_PREFIX, "").split("/"); - [ - autosave.username, - autosave.branch, - autosave.commit, - autosave.finalCommit, - ] = autosaveData; - return { ...branch, autosave }; - }); - const standard = branches.filter( - (branch) => !branch.name.startsWith(AUTOSAVED_PREFIX) - ); - return { standard, autosaved }; -} - -function simpleHash(str) { - let i, - l, - hVal = 0x0128a9d4; - for (i = 0, l = str.length; i < l; i++) { - hVal ^= str.charCodeAt(i); - hVal += - (hVal << 1) + (hVal << 4) + (hVal << 7) + (hVal << 8) + (hVal << 24); - } - return ("0000000" + (hVal >>> 0).toString(16)).substr(-8); -} - -function parseINIString(data) { - const regex = { - section: /^\s*\[\s*([^\]]*)\s*\]\s*$/, - param: /^\s*([^=]+?)\s*=\s*(.*?)\s*$/, - comment: /^\s*;.*$/, - }; - const lines = data.split(/[\r\n]+/); - let section = null; - let value = {}; - lines.forEach(function (line) { - if (regex.comment.test(line)) { - return; - } else if (regex.param.test(line)) { - let match = line.match(regex.param); - if (section) value[section][match[1]] = match[2]; - else value[match[1]] = match[2]; - } else if (regex.section.test(line)) { - let match = line.match(regex.section); - value[match[1]] = {}; - section = match[1]; - } else if (line.length === 0 && section) { - section = null; - } - }); - return value; -} - -/** - * Return a human readable number of bytes or its power. - * - * @param {number} bytes - Number to render as human readable - * @param {number} decimals - Number of decimals - */ -function formatBytes(bytes, decimals = 2) { - if (bytes === 0) return "0 Bytes"; - - // this prevents the function to break on negative numbers, even if they are not particularly interesting - let sign = ""; - if (bytes < 0) { - sign = "-"; - bytes = -bytes; - } - - const k = 1024; - const dm = decimals < 0 ? 0 : decimals; - const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - - const i = Math.floor(Math.log(bytes) / Math.log(k)); - if (isNaN(i)) return i.toString(); - - return ( - sign + parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i] - ); -} - -/** - * Function to group an array per key getter, returns a grouped map - * @param {*} list array to be grouped - * @param {*} keyGetter function that returns the key from an item inside the list - */ -function groupBy(list, keyGetter) { - const map = new Map(); - list.forEach((item) => { - const key = keyGetter(item); - const collection = map.get(key); - if (!collection) map.set(key, [item]); - else collection.push(item); - }); - return map; -} - -function gitLabUrlFromProfileUrl(webUrl) { - const comps = webUrl.split("/"); - comps.pop(); - return comps.join("/"); -} - -function isValidURL(url) { - try { - new URL(url); - return true; - } catch (error) { - return false; - } -} - -/** - * Refresh the data when they are not available or older than `tolerance`, preventing simultaneous invokations. - * - * @param {boolean} fetching - whether any fetching operation is currently ongoing. - * @param {date} fetched - last fetcheing date. - * @param {function} action -function to invoke to refresh the data. That should automatically change - * `fetched` and `fetching` - * @param {number} [tolerance] - Maximum age (in seconds) of the data before refreshing them. Default is 10. - */ -function refreshIfNecessary(fetching, fetched, action, tolerance = 10) { - if (fetching) return; - const now = new Date(); - if (!fetched || now - fetched > tolerance * 1000) return action(); -} - -/** - * Simulate a sleep function. - * @param {number} seconds - length of the sleep time span in seconds - * @example await sleep(0.5) // sleep for 0.5 seconds - */ -async function sleep(seconds) { - await new Promise((r) => setTimeout(r, seconds * 1000)); -} - /** * Generate a .zip file and save it * @param {object} files - files to include in the .zip, It has the format [{ name, content }...] @@ -271,103 +129,4 @@ const generateZip = async (files, name) => { saveAs(content, `${name}.zip`); }; -/** - * Capitalize the first character of all words in a string - * @param {string} text - text to capitalize - * @example capitalizeFirstLetter("hello world!") returns "Hello World" - */ -const capitalizeFirstLetter = (text) => - text.charAt(0).toUpperCase() + text.toLowerCase().slice(1); - -/** - * Capitalize a string. - * @param {string} aString - */ -function toCapitalized(aString) { - return aString.charAt(0).toUpperCase() + aString.slice(1); -} - -/** - * computeVisibilities. - * @param {string[]} options - */ -const computeVisibilities = (options) => { - if (options.includes("private")) { - return { - visibilities: ["private"], - disabled: ["public", "internal"], - default: "private", - }; - } else if (options.includes("internal")) { - return { - visibilities: ["private", "internal"], - disabled: ["public"], - default: "internal", - }; - } - return { - visibilities: ["private", "internal", "public"], - disabled: [], - default: "public", - }; -}; - -function stylesByItemType(itemType) { - switch (itemType) { - case "project": - return { - colorText: "text-rk-green", - bgColor: "rk-green", - }; - case "dataset": - return { - colorText: "text-rk-pink", - bgColor: "rk-pink", - }; - case "workflow": - return { - colorText: "text-rk-yellow", - bgColor: "rk-yellow", - }; - default: - return { - colorText: "text-rk-green", - bgColor: "rk-green", - }; - } -} - -/** - * Display a file with some metadata. Has the following parameters: - * - * @param {ImagesLinks[]} images - Images link arrays - */ -function getEntityImageUrl(images) { - try { - // images could contain previous image url, so we get the last in the array. - const index = images?.length - 1; - return images[index]["_links"][0].href; - } catch { - return undefined; - } -} - -export { - capitalizeFirstLetter, - generateZip, - computeVisibilities, - slugFromTitle, - splitAutosavedBranches, - simpleHash, - parseINIString, - formatBytes, - getEntityImageUrl, - groupBy, - gitLabUrlFromProfileUrl, - isValidURL, - convertUnicodeToAscii, - refreshIfNecessary, - sleep, - toCapitalized, - stylesByItemType, -}; +export { generateZip, slugFromTitle, convertUnicodeToAscii }; diff --git a/client/src/utils/types/coreService.types.ts b/client/src/utils/types/coreService.types.ts deleted file mode 100644 index 52f601d4c4..0000000000 --- a/client/src/utils/types/coreService.types.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*! - * Copyright 2023 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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. - */ - -export interface CoreErrorContent { - code: number; - devMessage: string; - devReference?: string; - sentry?: string; - userMessage: string; - userReference?: string; -} - -export interface CoreResponse { - error?: CoreErrorContent; - result?: T; -} diff --git a/client/src/websocket/messageDispatch.ts b/client/src/websocket/messageDispatch.ts index 3a67e28817..031abddf98 100644 --- a/client/src/websocket/messageDispatch.ts +++ b/client/src/websocket/messageDispatch.ts @@ -39,7 +39,7 @@ type MessageHandlersType = Record< Record | undefined> | undefined >; -export const MESSAGE_HANDLERS = { +const MESSAGE_HANDLERS = { user: { init: [ {