mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-12 13:48:35 +02:00
fix: yarn lint:fix (#4917)
## About the changes Running yarn lint:fix solves errors in frontend
This commit is contained in:
parent
c1f8929ddf
commit
bd8b54b5bd
@ -1,57 +1,57 @@
|
|||||||
import { render, screen, waitFor } from "@testing-library/react";
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
import { MemoryRouter, Routes, Route } from "react-router-dom";
|
import { MemoryRouter, Routes, Route } from 'react-router-dom';
|
||||||
import { FeatureView } from "../feature/FeatureView/FeatureView";
|
import { FeatureView } from '../feature/FeatureView/FeatureView';
|
||||||
import { ThemeProvider } from "themes/ThemeProvider";
|
import { ThemeProvider } from 'themes/ThemeProvider';
|
||||||
import { AccessProvider } from "../providers/AccessProvider/AccessProvider";
|
import { AccessProvider } from '../providers/AccessProvider/AccessProvider';
|
||||||
import { AnnouncerProvider } from "../common/Announcer/AnnouncerProvider/AnnouncerProvider";
|
import { AnnouncerProvider } from '../common/Announcer/AnnouncerProvider/AnnouncerProvider';
|
||||||
import { testServerRoute, testServerSetup } from "../../utils/testServer";
|
import { testServerRoute, testServerSetup } from '../../utils/testServer';
|
||||||
import { UIProviderContainer } from "../providers/UIProvider/UIProviderContainer";
|
import { UIProviderContainer } from '../providers/UIProvider/UIProviderContainer';
|
||||||
import { FC } from "react";
|
import { FC } from 'react';
|
||||||
import { IPermission } from "../../interfaces/user";
|
import { IPermission } from '../../interfaces/user';
|
||||||
import { SWRConfig } from "swr";
|
import { SWRConfig } from 'swr';
|
||||||
import { ProjectMode } from "../project/Project/hooks/useProjectEnterpriseSettingsForm";
|
import { ProjectMode } from '../project/Project/hooks/useProjectEnterpriseSettingsForm';
|
||||||
|
|
||||||
const server = testServerSetup();
|
const server = testServerSetup();
|
||||||
|
|
||||||
const projectWithCollaborationMode = (mode: ProjectMode) =>
|
const projectWithCollaborationMode = (mode: ProjectMode) =>
|
||||||
testServerRoute(server, "/api/admin/projects/default", { mode });
|
testServerRoute(server, '/api/admin/projects/default', { mode });
|
||||||
|
|
||||||
const changeRequestsEnabledIn = (
|
const changeRequestsEnabledIn = (
|
||||||
env: "development" | "production" | "custom"
|
env: 'development' | 'production' | 'custom',
|
||||||
) =>
|
) =>
|
||||||
testServerRoute(
|
testServerRoute(
|
||||||
server,
|
server,
|
||||||
"/api/admin/projects/default/change-requests/config",
|
'/api/admin/projects/default/change-requests/config',
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
environment: "development",
|
environment: 'development',
|
||||||
type: "development",
|
type: 'development',
|
||||||
requiredApprovals: null,
|
requiredApprovals: null,
|
||||||
changeRequestEnabled: env === "development",
|
changeRequestEnabled: env === 'development',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
environment: "production",
|
environment: 'production',
|
||||||
type: "production",
|
type: 'production',
|
||||||
requiredApprovals: 1,
|
requiredApprovals: 1,
|
||||||
changeRequestEnabled: env === "production",
|
changeRequestEnabled: env === 'production',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
environment: "custom",
|
environment: 'custom',
|
||||||
type: "production",
|
type: 'production',
|
||||||
requiredApprovals: null,
|
requiredApprovals: null,
|
||||||
changeRequestEnabled: env === "custom",
|
changeRequestEnabled: env === 'custom',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const uiConfigForEnterprise = () =>
|
const uiConfigForEnterprise = () =>
|
||||||
testServerRoute(server, "/api/admin/ui-config", {
|
testServerRoute(server, '/api/admin/ui-config', {
|
||||||
environment: "Open Source",
|
environment: 'Open Source',
|
||||||
flags: {
|
flags: {
|
||||||
changeRequests: true,
|
changeRequests: true,
|
||||||
},
|
},
|
||||||
versionInfo: {
|
versionInfo: {
|
||||||
current: { oss: "4.18.0-beta.5", enterprise: "4.17.0-beta.1" },
|
current: { oss: '4.18.0-beta.5', enterprise: '4.17.0-beta.1' },
|
||||||
},
|
},
|
||||||
disablePasswordAuth: false,
|
disablePasswordAuth: false,
|
||||||
});
|
});
|
||||||
@ -59,12 +59,12 @@ const uiConfigForEnterprise = () =>
|
|||||||
const setupOtherRoutes = (feature: string) => {
|
const setupOtherRoutes = (feature: string) => {
|
||||||
testServerRoute(
|
testServerRoute(
|
||||||
server,
|
server,
|
||||||
"api/admin/projects/default/change-requests/pending",
|
'api/admin/projects/default/change-requests/pending',
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
testServerRoute(server, `api/admin/client-metrics/features/${feature}`, {
|
testServerRoute(server, `api/admin/client-metrics/features/${feature}`, {
|
||||||
version: 1,
|
version: 1,
|
||||||
maturity: "stable",
|
maturity: 'stable',
|
||||||
featureName: feature,
|
featureName: feature,
|
||||||
lastHourUsage: [],
|
lastHourUsage: [],
|
||||||
seenApplications: [],
|
seenApplications: [],
|
||||||
@ -86,25 +86,25 @@ const setupOtherRoutes = (feature: string) => {
|
|||||||
version: 1,
|
version: 1,
|
||||||
strategies: [
|
strategies: [
|
||||||
{
|
{
|
||||||
displayName: "Standard",
|
displayName: 'Standard',
|
||||||
name: "default",
|
name: 'default',
|
||||||
editable: false,
|
editable: false,
|
||||||
description:
|
description:
|
||||||
"The standard strategy is strictly on / off for your entire userbase.",
|
'The standard strategy is strictly on / off for your entire userbase.',
|
||||||
parameters: [],
|
parameters: [],
|
||||||
deprecated: false,
|
deprecated: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: "UserIDs",
|
displayName: 'UserIDs',
|
||||||
name: "userWithId",
|
name: 'userWithId',
|
||||||
editable: false,
|
editable: false,
|
||||||
description:
|
description:
|
||||||
"Enable the feature for a specific set of userIds.",
|
'Enable the feature for a specific set of userIds.',
|
||||||
parameters: [
|
parameters: [
|
||||||
{
|
{
|
||||||
name: "userIds",
|
name: 'userIds',
|
||||||
type: "list",
|
type: 'list',
|
||||||
description: "",
|
description: '',
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -115,17 +115,17 @@ const setupOtherRoutes = (feature: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const userHasPermissions = (permissions: Array<IPermission>) => {
|
const userHasPermissions = (permissions: Array<IPermission>) => {
|
||||||
testServerRoute(server, "api/admin/user", {
|
testServerRoute(server, 'api/admin/user', {
|
||||||
user: {
|
user: {
|
||||||
isAPI: false,
|
isAPI: false,
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Test",
|
name: 'Test',
|
||||||
email: "test@getunleash.ai",
|
email: 'test@getunleash.ai',
|
||||||
imageUrl:
|
imageUrl:
|
||||||
"https://gravatar.com/avatar/e55646b526ff342ff8b43721f0cbdd8e?size=42&default=retro",
|
'https://gravatar.com/avatar/e55646b526ff342ff8b43721f0cbdd8e?size=42&default=retro',
|
||||||
seenAt: "2022-11-29T08:21:52.581Z",
|
seenAt: '2022-11-29T08:21:52.581Z',
|
||||||
loginAttempts: 0,
|
loginAttempts: 0,
|
||||||
createdAt: "2022-11-21T10:10:33.074Z",
|
createdAt: '2022-11-21T10:10:33.074Z',
|
||||||
},
|
},
|
||||||
permissions,
|
permissions,
|
||||||
feedback: [],
|
feedback: [],
|
||||||
@ -136,21 +136,21 @@ const userIsMemberOfProjects = (projects: string[]) => {
|
|||||||
userHasPermissions(
|
userHasPermissions(
|
||||||
projects.map((project) => ({
|
projects.map((project) => ({
|
||||||
project,
|
project,
|
||||||
environment: "irrelevant",
|
environment: 'irrelevant',
|
||||||
permission: "irrelevant",
|
permission: 'irrelevant',
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const featureEnvironments = (
|
const featureEnvironments = (
|
||||||
feature: string,
|
feature: string,
|
||||||
environments: Array<{ name: string; strategies: Array<string> }>
|
environments: Array<{ name: string; strategies: Array<string> }>,
|
||||||
) => {
|
) => {
|
||||||
testServerRoute(server, `/api/admin/projects/default/features/${feature}`, {
|
testServerRoute(server, `/api/admin/projects/default/features/${feature}`, {
|
||||||
environments: environments.map((env) => ({
|
environments: environments.map((env) => ({
|
||||||
name: env.name,
|
name: env.name,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
type: "production",
|
type: 'production',
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
strategies: env.strategies.map((strategy) => ({
|
strategies: env.strategies.map((strategy) => ({
|
||||||
name: strategy,
|
name: strategy,
|
||||||
@ -162,13 +162,13 @@ const featureEnvironments = (
|
|||||||
})),
|
})),
|
||||||
name: feature,
|
name: feature,
|
||||||
impressionData: false,
|
impressionData: false,
|
||||||
description: "",
|
description: '',
|
||||||
project: "default",
|
project: 'default',
|
||||||
stale: false,
|
stale: false,
|
||||||
variants: [],
|
variants: [],
|
||||||
createdAt: "2022-11-14T08:16:33.338Z",
|
createdAt: '2022-11-14T08:16:33.338Z',
|
||||||
lastSeenAt: null,
|
lastSeenAt: null,
|
||||||
type: "release",
|
type: 'release',
|
||||||
archived: false,
|
archived: false,
|
||||||
children: [],
|
children: [],
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
@ -199,7 +199,7 @@ const UnleashUiSetup: FC<{ path: string; pathTemplate: string }> = ({
|
|||||||
|
|
||||||
const strategiesAreDisplayed = async (
|
const strategiesAreDisplayed = async (
|
||||||
firstStrategy: string,
|
firstStrategy: string,
|
||||||
secondStrategy: string
|
secondStrategy: string,
|
||||||
) => {
|
) => {
|
||||||
await screen.findByText(firstStrategy);
|
await screen.findByText(firstStrategy);
|
||||||
await screen.findByText(secondStrategy);
|
await screen.findByText(secondStrategy);
|
||||||
@ -213,10 +213,10 @@ const getDeleteButtons = async () => {
|
|||||||
removeMenus.map(async (menu) => {
|
removeMenus.map(async (menu) => {
|
||||||
menu.click();
|
menu.click();
|
||||||
const removeButton = screen.getAllByTestId(
|
const removeButton = screen.getAllByTestId(
|
||||||
"STRATEGY_FORM_REMOVE_ID"
|
'STRATEGY_FORM_REMOVE_ID',
|
||||||
);
|
);
|
||||||
deleteButtons.push(...removeButton);
|
deleteButtons.push(...removeButton);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
return deleteButtons;
|
return deleteButtons;
|
||||||
};
|
};
|
||||||
@ -229,12 +229,12 @@ const deleteButtonsActiveInChangeRequestEnv = async () => {
|
|||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
// production
|
// production
|
||||||
const productionStrategyDeleteButton = deleteButtons[1];
|
const productionStrategyDeleteButton = deleteButtons[1];
|
||||||
expect(productionStrategyDeleteButton).not.toHaveClass("Mui-disabled");
|
expect(productionStrategyDeleteButton).not.toHaveClass('Mui-disabled');
|
||||||
});
|
});
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
// custom env
|
// custom env
|
||||||
const customEnvStrategyDeleteButton = deleteButtons[2];
|
const customEnvStrategyDeleteButton = deleteButtons[2];
|
||||||
expect(customEnvStrategyDeleteButton).toHaveClass("Mui-disabled");
|
expect(customEnvStrategyDeleteButton).toHaveClass('Mui-disabled');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -246,17 +246,17 @@ const deleteButtonsInactiveInChangeRequestEnv = async () => {
|
|||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
// production
|
// production
|
||||||
const productionStrategyDeleteButton = deleteButtons[1];
|
const productionStrategyDeleteButton = deleteButtons[1];
|
||||||
expect(productionStrategyDeleteButton).toHaveClass("Mui-disabled");
|
expect(productionStrategyDeleteButton).toHaveClass('Mui-disabled');
|
||||||
});
|
});
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
// custom env
|
// custom env
|
||||||
const customEnvStrategyDeleteButton = deleteButtons[2];
|
const customEnvStrategyDeleteButton = deleteButtons[2];
|
||||||
expect(customEnvStrategyDeleteButton).toHaveClass("Mui-disabled");
|
expect(customEnvStrategyDeleteButton).toHaveClass('Mui-disabled');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyButtonsActiveInOtherEnv = async () => {
|
const copyButtonsActiveInOtherEnv = async () => {
|
||||||
const copyButtons = screen.getAllByTestId("STRATEGY_FORM_COPY_ID");
|
const copyButtons = screen.getAllByTestId('STRATEGY_FORM_COPY_ID');
|
||||||
expect(copyButtons.length).toBe(2);
|
expect(copyButtons.length).toBe(2);
|
||||||
|
|
||||||
// production
|
// production
|
||||||
@ -274,92 +274,92 @@ const openEnvironments = async (envNames: string[]) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
test("open mode + non-project member can perform basic change request actions", async () => {
|
test('open mode + non-project member can perform basic change request actions', async () => {
|
||||||
const project = "default";
|
const project = 'default';
|
||||||
const featureName = "test";
|
const featureName = 'test';
|
||||||
featureEnvironments(featureName, [
|
featureEnvironments(featureName, [
|
||||||
{ name: "development", strategies: [] },
|
{ name: 'development', strategies: [] },
|
||||||
{ name: "production", strategies: ["userWithId"] },
|
{ name: 'production', strategies: ['userWithId'] },
|
||||||
{ name: "custom", strategies: ["default"] },
|
{ name: 'custom', strategies: ['default'] },
|
||||||
]);
|
]);
|
||||||
userIsMemberOfProjects([]);
|
userIsMemberOfProjects([]);
|
||||||
changeRequestsEnabledIn("production");
|
changeRequestsEnabledIn('production');
|
||||||
projectWithCollaborationMode("open");
|
projectWithCollaborationMode('open');
|
||||||
uiConfigForEnterprise();
|
uiConfigForEnterprise();
|
||||||
setupOtherRoutes(featureName);
|
setupOtherRoutes(featureName);
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<UnleashUiSetup
|
<UnleashUiSetup
|
||||||
pathTemplate="/projects/:projectId/features/:featureId/*"
|
pathTemplate='/projects/:projectId/features/:featureId/*'
|
||||||
path={`/projects/${project}/features/${featureName}`}
|
path={`/projects/${project}/features/${featureName}`}
|
||||||
>
|
>
|
||||||
<FeatureView />
|
<FeatureView />
|
||||||
</UnleashUiSetup>
|
</UnleashUiSetup>,
|
||||||
);
|
);
|
||||||
|
|
||||||
await openEnvironments(["development", "production", "custom"]);
|
await openEnvironments(['development', 'production', 'custom']);
|
||||||
|
|
||||||
await strategiesAreDisplayed("UserIDs", "Standard");
|
await strategiesAreDisplayed('UserIDs', 'Standard');
|
||||||
await deleteButtonsActiveInChangeRequestEnv();
|
await deleteButtonsActiveInChangeRequestEnv();
|
||||||
await copyButtonsActiveInOtherEnv();
|
await copyButtonsActiveInOtherEnv();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("protected mode + project member can perform basic change request actions", async () => {
|
test('protected mode + project member can perform basic change request actions', async () => {
|
||||||
const project = "default";
|
const project = 'default';
|
||||||
const featureName = "test";
|
const featureName = 'test';
|
||||||
featureEnvironments(featureName, [
|
featureEnvironments(featureName, [
|
||||||
{ name: "development", strategies: [] },
|
{ name: 'development', strategies: [] },
|
||||||
{ name: "production", strategies: ["userWithId"] },
|
{ name: 'production', strategies: ['userWithId'] },
|
||||||
{ name: "custom", strategies: ["default"] },
|
{ name: 'custom', strategies: ['default'] },
|
||||||
]);
|
]);
|
||||||
userIsMemberOfProjects([project]);
|
userIsMemberOfProjects([project]);
|
||||||
changeRequestsEnabledIn("production");
|
changeRequestsEnabledIn('production');
|
||||||
projectWithCollaborationMode("protected");
|
projectWithCollaborationMode('protected');
|
||||||
uiConfigForEnterprise();
|
uiConfigForEnterprise();
|
||||||
setupOtherRoutes(featureName);
|
setupOtherRoutes(featureName);
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<UnleashUiSetup
|
<UnleashUiSetup
|
||||||
pathTemplate="/projects/:projectId/features/:featureId/*"
|
pathTemplate='/projects/:projectId/features/:featureId/*'
|
||||||
path={`/projects/${project}/features/${featureName}`}
|
path={`/projects/${project}/features/${featureName}`}
|
||||||
>
|
>
|
||||||
<FeatureView />
|
<FeatureView />
|
||||||
</UnleashUiSetup>
|
</UnleashUiSetup>,
|
||||||
);
|
);
|
||||||
|
|
||||||
await openEnvironments(["development", "production", "custom"]);
|
await openEnvironments(['development', 'production', 'custom']);
|
||||||
|
|
||||||
await strategiesAreDisplayed("UserIDs", "Standard");
|
await strategiesAreDisplayed('UserIDs', 'Standard');
|
||||||
await deleteButtonsActiveInChangeRequestEnv();
|
await deleteButtonsActiveInChangeRequestEnv();
|
||||||
await copyButtonsActiveInOtherEnv();
|
await copyButtonsActiveInOtherEnv();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("protected mode + non-project member cannot perform basic change request actions", async () => {
|
test('protected mode + non-project member cannot perform basic change request actions', async () => {
|
||||||
const project = "default";
|
const project = 'default';
|
||||||
const featureName = "test";
|
const featureName = 'test';
|
||||||
featureEnvironments(featureName, [
|
featureEnvironments(featureName, [
|
||||||
{ name: "development", strategies: [] },
|
{ name: 'development', strategies: [] },
|
||||||
{ name: "production", strategies: ["userWithId"] },
|
{ name: 'production', strategies: ['userWithId'] },
|
||||||
{ name: "custom", strategies: ["default"] },
|
{ name: 'custom', strategies: ['default'] },
|
||||||
]);
|
]);
|
||||||
userIsMemberOfProjects([]);
|
userIsMemberOfProjects([]);
|
||||||
changeRequestsEnabledIn("production");
|
changeRequestsEnabledIn('production');
|
||||||
projectWithCollaborationMode("protected");
|
projectWithCollaborationMode('protected');
|
||||||
uiConfigForEnterprise();
|
uiConfigForEnterprise();
|
||||||
setupOtherRoutes(featureName);
|
setupOtherRoutes(featureName);
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<UnleashUiSetup
|
<UnleashUiSetup
|
||||||
pathTemplate="/projects/:projectId/features/:featureId/*"
|
pathTemplate='/projects/:projectId/features/:featureId/*'
|
||||||
path={`/projects/${project}/features/${featureName}`}
|
path={`/projects/${project}/features/${featureName}`}
|
||||||
>
|
>
|
||||||
<FeatureView />
|
<FeatureView />
|
||||||
</UnleashUiSetup>
|
</UnleashUiSetup>,
|
||||||
);
|
);
|
||||||
|
|
||||||
await openEnvironments(["development", "production", "custom"]);
|
await openEnvironments(['development', 'production', 'custom']);
|
||||||
|
|
||||||
await strategiesAreDisplayed("UserIDs", "Standard");
|
await strategiesAreDisplayed('UserIDs', 'Standard');
|
||||||
await deleteButtonsInactiveInChangeRequestEnv();
|
await deleteButtonsInactiveInChangeRequestEnv();
|
||||||
await copyButtonsActiveInOtherEnv();
|
await copyButtonsActiveInOtherEnv();
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import MenuBookIcon from "@mui/icons-material/MenuBook";
|
import MenuBookIcon from '@mui/icons-material/MenuBook';
|
||||||
import Codebox from "../Codebox/Codebox";
|
import Codebox from '../Codebox/Codebox';
|
||||||
import {
|
import {
|
||||||
Collapse,
|
Collapse,
|
||||||
IconButton,
|
IconButton,
|
||||||
@ -7,16 +7,16 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Divider,
|
Divider,
|
||||||
styled,
|
styled,
|
||||||
} from "@mui/material";
|
} from '@mui/material';
|
||||||
import { FileCopy, Info } from "@mui/icons-material";
|
import { FileCopy, Info } from '@mui/icons-material';
|
||||||
import { ConditionallyRender } from "component/common/ConditionallyRender/ConditionallyRender";
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import Loader from "../Loader/Loader";
|
import Loader from '../Loader/Loader';
|
||||||
import copy from "copy-to-clipboard";
|
import copy from 'copy-to-clipboard';
|
||||||
import useToast from "hooks/useToast";
|
import useToast from 'hooks/useToast';
|
||||||
import React, { ReactNode, useState } from "react";
|
import React, { ReactNode, useState } from 'react';
|
||||||
import { ReactComponent as MobileGuidanceBG } from "assets/img/mobileGuidanceBg.svg";
|
import { ReactComponent as MobileGuidanceBG } from 'assets/img/mobileGuidanceBg.svg';
|
||||||
import { formTemplateSidebarWidth } from "./FormTemplate.styles";
|
import { formTemplateSidebarWidth } from './FormTemplate.styles';
|
||||||
import { relative } from "themes/themeStyles";
|
import { relative } from 'themes/themeStyles';
|
||||||
|
|
||||||
interface ICreateProps {
|
interface ICreateProps {
|
||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
@ -34,66 +34,66 @@ interface ICreateProps {
|
|||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledContainer = styled("section", {
|
const StyledContainer = styled('section', {
|
||||||
shouldForwardProp: (prop) =>
|
shouldForwardProp: (prop) =>
|
||||||
!["modal", "compact"].includes(prop.toString()),
|
!['modal', 'compact'].includes(prop.toString()),
|
||||||
})<{ modal?: boolean; compact?: boolean }>(({ theme, modal, compact }) => ({
|
})<{ modal?: boolean; compact?: boolean }>(({ theme, modal, compact }) => ({
|
||||||
minHeight: modal ? "100vh" : compact ? 0 : "80vh",
|
minHeight: modal ? '100vh' : compact ? 0 : '80vh',
|
||||||
borderRadius: modal ? 0 : theme.spacing(2),
|
borderRadius: modal ? 0 : theme.spacing(2),
|
||||||
width: "100%",
|
width: '100%',
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
margin: "0 auto",
|
margin: '0 auto',
|
||||||
overflow: modal ? "unset" : "hidden",
|
overflow: modal ? 'unset' : 'hidden',
|
||||||
[theme.breakpoints.down(1100)]: {
|
[theme.breakpoints.down(1100)]: {
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledRelativeDiv = styled("div")(({ theme }) => relative);
|
const StyledRelativeDiv = styled('div')(({ theme }) => relative);
|
||||||
|
|
||||||
const StyledMain = styled("div")(({ theme }) => ({
|
const StyledMain = styled('div')(({ theme }) => ({
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
width: "100%",
|
width: '100%',
|
||||||
[theme.breakpoints.down(1100)]: {
|
[theme.breakpoints.down(1100)]: {
|
||||||
width: "100%",
|
width: '100%',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledFormContent = styled("div", {
|
const StyledFormContent = styled('div', {
|
||||||
shouldForwardProp: (prop) => {
|
shouldForwardProp: (prop) => {
|
||||||
return !["disablePadding", "compactPadding"].includes(prop.toString());
|
return !['disablePadding', 'compactPadding'].includes(prop.toString());
|
||||||
},
|
},
|
||||||
})<{ disablePadding?: boolean; compactPadding?: boolean }>(
|
})<{ disablePadding?: boolean; compactPadding?: boolean }>(
|
||||||
({ theme, disablePadding, compactPadding }) => ({
|
({ theme, disablePadding, compactPadding }) => ({
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.paper,
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
padding: disablePadding
|
padding: disablePadding
|
||||||
? 0
|
? 0
|
||||||
: compactPadding
|
: compactPadding
|
||||||
? theme.spacing(4)
|
? theme.spacing(4)
|
||||||
: theme.spacing(6),
|
: theme.spacing(6),
|
||||||
[theme.breakpoints.down("lg")]: {
|
[theme.breakpoints.down('lg')]: {
|
||||||
padding: disablePadding ? 0 : theme.spacing(4),
|
padding: disablePadding ? 0 : theme.spacing(4),
|
||||||
},
|
},
|
||||||
[theme.breakpoints.down(1100)]: {
|
[theme.breakpoints.down(1100)]: {
|
||||||
width: "100%",
|
width: '100%',
|
||||||
},
|
},
|
||||||
[theme.breakpoints.down(500)]: {
|
[theme.breakpoints.down(500)]: {
|
||||||
padding: disablePadding ? 0 : theme.spacing(4, 2),
|
padding: disablePadding ? 0 : theme.spacing(4, 2),
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const StyledFooter = styled("div")(({ theme }) => ({
|
const StyledFooter = styled('div')(({ theme }) => ({
|
||||||
backgroundColor: theme.palette.background.paper,
|
backgroundColor: theme.palette.background.paper,
|
||||||
padding: theme.spacing(4, 6),
|
padding: theme.spacing(4, 6),
|
||||||
[theme.breakpoints.down("lg")]: {
|
[theme.breakpoints.down('lg')]: {
|
||||||
padding: theme.spacing(4),
|
padding: theme.spacing(4),
|
||||||
},
|
},
|
||||||
[theme.breakpoints.down(500)]: {
|
[theme.breakpoints.down(500)]: {
|
||||||
@ -101,9 +101,9 @@ const StyledFooter = styled("div")(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledTitle = styled("h1")(({ theme }) => ({
|
const StyledTitle = styled('h1')(({ theme }) => ({
|
||||||
marginBottom: theme.fontSizes.mainHeader,
|
marginBottom: theme.fontSizes.mainHeader,
|
||||||
fontWeight: "normal",
|
fontWeight: 'normal',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledSidebarDivider = styled(Divider)(({ theme }) => ({
|
const StyledSidebarDivider = styled(Divider)(({ theme }) => ({
|
||||||
@ -111,12 +111,12 @@ const StyledSidebarDivider = styled(Divider)(({ theme }) => ({
|
|||||||
marginBottom: theme.spacing(0.5),
|
marginBottom: theme.spacing(0.5),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledSubtitle = styled("h2")(({ theme }) => ({
|
const StyledSubtitle = styled('h2')(({ theme }) => ({
|
||||||
color: theme.palette.common.white,
|
color: theme.palette.common.white,
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
justifyContent: "space-between",
|
justifyContent: 'space-between',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
fontWeight: theme.fontWeight.bold,
|
fontWeight: theme.fontWeight.bold,
|
||||||
fontSize: theme.fontSizes.bodySize,
|
fontSize: theme.fontSizes.bodySize,
|
||||||
}));
|
}));
|
||||||
@ -125,20 +125,20 @@ const StyledIcon = styled(FileCopy)(({ theme }) => ({
|
|||||||
fill: theme.palette.primary.contrastText,
|
fill: theme.palette.primary.contrastText,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledMobileGuidanceContainer = styled("div")(() => ({
|
const StyledMobileGuidanceContainer = styled('div')(() => ({
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
right: -3,
|
right: -3,
|
||||||
top: -3,
|
top: -3,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledMobileGuidanceBackground = styled(MobileGuidanceBG)(() => ({
|
const StyledMobileGuidanceBackground = styled(MobileGuidanceBG)(() => ({
|
||||||
width: "75px",
|
width: '75px',
|
||||||
height: "75px",
|
height: '75px',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledMobileGuidanceButton = styled(IconButton)(() => ({
|
const StyledMobileGuidanceButton = styled(IconButton)(() => ({
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
zIndex: 400,
|
zIndex: 400,
|
||||||
right: 0,
|
right: 0,
|
||||||
}));
|
}));
|
||||||
@ -147,31 +147,31 @@ const StyledInfoIcon = styled(Info)(({ theme }) => ({
|
|||||||
fill: theme.palette.primary.contrastText,
|
fill: theme.palette.primary.contrastText,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledSidebar = styled("aside")(({ theme }) => ({
|
const StyledSidebar = styled('aside')(({ theme }) => ({
|
||||||
backgroundColor: theme.palette.background.sidebar,
|
backgroundColor: theme.palette.background.sidebar,
|
||||||
padding: theme.spacing(4),
|
padding: theme.spacing(4),
|
||||||
flexGrow: 0,
|
flexGrow: 0,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
width: formTemplateSidebarWidth,
|
width: formTemplateSidebarWidth,
|
||||||
[theme.breakpoints.down(1100)]: {
|
[theme.breakpoints.down(1100)]: {
|
||||||
width: "100%",
|
width: '100%',
|
||||||
color: "red",
|
color: 'red',
|
||||||
},
|
},
|
||||||
[theme.breakpoints.down(500)]: {
|
[theme.breakpoints.down(500)]: {
|
||||||
padding: theme.spacing(4, 2),
|
padding: theme.spacing(4, 2),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledDescription = styled("p")(({ theme }) => ({
|
const StyledDescription = styled('p')(({ theme }) => ({
|
||||||
color: theme.palette.common.white,
|
color: theme.palette.common.white,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
position: "relative",
|
position: 'relative',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledLinkContainer = styled("div")(({ theme }) => ({
|
const StyledLinkContainer = styled('div')(({ theme }) => ({
|
||||||
margin: theme.spacing(3, 0),
|
margin: theme.spacing(3, 0),
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledLinkIcon = styled(MenuBookIcon)(({ theme }) => ({
|
const StyledLinkIcon = styled(MenuBookIcon)(({ theme }) => ({
|
||||||
@ -179,11 +179,11 @@ const StyledLinkIcon = styled(MenuBookIcon)(({ theme }) => ({
|
|||||||
color: theme.palette.primary.contrastText,
|
color: theme.palette.primary.contrastText,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledDocumentationLink = styled("a")(({ theme }) => ({
|
const StyledDocumentationLink = styled('a')(({ theme }) => ({
|
||||||
color: theme.palette.primary.contrastText,
|
color: theme.palette.primary.contrastText,
|
||||||
display: "block",
|
display: 'block',
|
||||||
"&:hover": {
|
'&:hover': {
|
||||||
textDecoration: "none",
|
textDecoration: 'none',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -209,18 +209,18 @@ const FormTemplate: React.FC<ICreateProps> = ({
|
|||||||
if (formatApiCode !== undefined) {
|
if (formatApiCode !== undefined) {
|
||||||
if (copy(formatApiCode())) {
|
if (copy(formatApiCode())) {
|
||||||
setToastData({
|
setToastData({
|
||||||
title: "Successfully copied the command",
|
title: 'Successfully copied the command',
|
||||||
text: "The command should now be automatically copied to your clipboard",
|
text: 'The command should now be automatically copied to your clipboard',
|
||||||
autoHideDuration: 6000,
|
autoHideDuration: 6000,
|
||||||
type: "success",
|
type: 'success',
|
||||||
show: true,
|
show: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setToastData({
|
setToastData({
|
||||||
title: "Could not copy the command",
|
title: 'Could not copy the command',
|
||||||
text: "Sorry, but we could not copy the command.",
|
text: 'Sorry, but we could not copy the command.',
|
||||||
autoHideDuration: 6000,
|
autoHideDuration: 6000,
|
||||||
type: "error",
|
type: 'error',
|
||||||
show: true,
|
show: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -236,14 +236,14 @@ const FormTemplate: React.FC<ICreateProps> = ({
|
|||||||
show={<StyledSidebarDivider />}
|
show={<StyledSidebarDivider />}
|
||||||
/>
|
/>
|
||||||
<StyledSubtitle>
|
<StyledSubtitle>
|
||||||
API Command{" "}
|
API Command{' '}
|
||||||
<Tooltip title="Copy command" arrow>
|
<Tooltip title='Copy command' arrow>
|
||||||
<IconButton onClick={copyCommand} size="large">
|
<IconButton onClick={copyCommand} size='large'>
|
||||||
<StyledIcon />
|
<StyledIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</StyledSubtitle>
|
</StyledSubtitle>
|
||||||
<Codebox text={formatApiCode!()} />{" "}
|
<Codebox text={formatApiCode!()} />{' '}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -304,7 +304,7 @@ const FormTemplate: React.FC<ICreateProps> = ({
|
|||||||
>
|
>
|
||||||
{renderApiInfo(
|
{renderApiInfo(
|
||||||
formatApiCode === undefined,
|
formatApiCode === undefined,
|
||||||
!(showDescription || showLink)
|
!(showDescription || showLink),
|
||||||
)}
|
)}
|
||||||
</Guidance>
|
</Guidance>
|
||||||
}
|
}
|
||||||
@ -331,10 +331,10 @@ const MobileGuidance = ({
|
|||||||
<StyledMobileGuidanceContainer>
|
<StyledMobileGuidanceContainer>
|
||||||
<StyledMobileGuidanceBackground />
|
<StyledMobileGuidanceBackground />
|
||||||
</StyledMobileGuidanceContainer>
|
</StyledMobileGuidanceContainer>
|
||||||
<Tooltip title="Toggle help" arrow>
|
<Tooltip title='Toggle help' arrow>
|
||||||
<StyledMobileGuidanceButton
|
<StyledMobileGuidanceButton
|
||||||
onClick={() => setOpen((prev) => !prev)}
|
onClick={() => setOpen((prev) => !prev)}
|
||||||
size="large"
|
size='large'
|
||||||
>
|
>
|
||||||
<StyledInfoIcon />
|
<StyledInfoIcon />
|
||||||
</StyledMobileGuidanceButton>
|
</StyledMobileGuidanceButton>
|
||||||
@ -362,7 +362,7 @@ const Guidance: React.FC<IGuidanceProps> = ({
|
|||||||
description,
|
description,
|
||||||
children,
|
children,
|
||||||
documentationLink,
|
documentationLink,
|
||||||
documentationLinkLabel = "Learn more",
|
documentationLinkLabel = 'Learn more',
|
||||||
showDescription = true,
|
showDescription = true,
|
||||||
showLink = true,
|
showLink = true,
|
||||||
}) => {
|
}) => {
|
||||||
@ -380,8 +380,8 @@ const Guidance: React.FC<IGuidanceProps> = ({
|
|||||||
<StyledLinkIcon />
|
<StyledLinkIcon />
|
||||||
<StyledDocumentationLink
|
<StyledDocumentationLink
|
||||||
href={documentationLink}
|
href={documentationLink}
|
||||||
rel="noopener noreferrer"
|
rel='noopener noreferrer'
|
||||||
target="_blank"
|
target='_blank'
|
||||||
>
|
>
|
||||||
{documentationLinkLabel}
|
{documentationLinkLabel}
|
||||||
</StyledDocumentationLink>
|
</StyledDocumentationLink>
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from 'react-router-dom';
|
||||||
import ProjectForm from "../ProjectForm/ProjectForm";
|
import ProjectForm from '../ProjectForm/ProjectForm';
|
||||||
import useProjectForm, {
|
import useProjectForm, {
|
||||||
DEFAULT_PROJECT_STICKINESS,
|
DEFAULT_PROJECT_STICKINESS,
|
||||||
} from "../hooks/useProjectForm";
|
} from '../hooks/useProjectForm';
|
||||||
import { CreateButton } from "component/common/CreateButton/CreateButton";
|
import { CreateButton } from 'component/common/CreateButton/CreateButton';
|
||||||
import FormTemplate from "component/common/FormTemplate/FormTemplate";
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import { CREATE_PROJECT } from "component/providers/AccessProvider/permissions";
|
import { CREATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
||||||
import useProjectApi from "hooks/api/actions/useProjectApi/useProjectApi";
|
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
||||||
import { useAuthUser } from "hooks/api/getters/useAuth/useAuthUser";
|
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||||
import useUiConfig from "hooks/api/getters/useUiConfig/useUiConfig";
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import useToast from "hooks/useToast";
|
import useToast from 'hooks/useToast';
|
||||||
import { formatUnknownError } from "utils/formatUnknownError";
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { GO_BACK } from "constants/navigate";
|
import { GO_BACK } from 'constants/navigate';
|
||||||
import { usePlausibleTracker } from "hooks/usePlausibleTracker";
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
import { Button, styled } from "@mui/material";
|
import { Button, styled } from '@mui/material';
|
||||||
|
|
||||||
const CREATE_PROJECT_BTN = "CREATE_PROJECT_BTN";
|
const CREATE_PROJECT_BTN = 'CREATE_PROJECT_BTN';
|
||||||
|
|
||||||
const StyledButton = styled(Button)(({ theme }) => ({
|
const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
marginLeft: theme.spacing(3),
|
marginLeft: theme.spacing(3),
|
||||||
@ -60,17 +60,17 @@ const CreateProject = () => {
|
|||||||
refetchUser();
|
refetchUser();
|
||||||
navigate(`/projects/${projectId}`);
|
navigate(`/projects/${projectId}`);
|
||||||
setToastData({
|
setToastData({
|
||||||
title: "Project created",
|
title: 'Project created',
|
||||||
text: "Now you can add toggles to this project",
|
text: 'Now you can add toggles to this project',
|
||||||
confetti: true,
|
confetti: true,
|
||||||
type: "success",
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (projectStickiness !== DEFAULT_PROJECT_STICKINESS) {
|
if (projectStickiness !== DEFAULT_PROJECT_STICKINESS) {
|
||||||
trackEvent("project_stickiness_set");
|
trackEvent('project_stickiness_set');
|
||||||
}
|
}
|
||||||
trackEvent("project-mode", {
|
trackEvent('project-mode', {
|
||||||
props: { mode: projectMode, action: "added" },
|
props: { mode: projectMode, action: 'added' },
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(error));
|
setToastApiError(formatUnknownError(error));
|
||||||
@ -79,9 +79,7 @@ const CreateProject = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formatApiCode = () => {
|
const formatApiCode = () => {
|
||||||
return `curl --location --request POST '${
|
return `curl --location --request POST '${uiConfig.unleashUrl}/api/admin/projects' \\
|
||||||
uiConfig.unleashUrl
|
|
||||||
}/api/admin/projects' \\
|
|
||||||
--header 'Authorization: INSERT_API_KEY' \\
|
--header 'Authorization: INSERT_API_KEY' \\
|
||||||
--header 'Content-Type: application/json' \\
|
--header 'Content-Type: application/json' \\
|
||||||
--data-raw '${JSON.stringify(getCreateProjectPayload(), undefined, 2)}'`;
|
--data-raw '${JSON.stringify(getCreateProjectPayload(), undefined, 2)}'`;
|
||||||
@ -94,10 +92,10 @@ const CreateProject = () => {
|
|||||||
return (
|
return (
|
||||||
<FormTemplate
|
<FormTemplate
|
||||||
loading={loading}
|
loading={loading}
|
||||||
title="Create project"
|
title='Create project'
|
||||||
description="Projects allows you to group feature toggles together in the management UI."
|
description='Projects allows you to group feature toggles together in the management UI.'
|
||||||
documentationLink="https://docs.getunleash.io/reference/projects"
|
documentationLink='https://docs.getunleash.io/reference/projects'
|
||||||
documentationLinkLabel="Projects documentation"
|
documentationLinkLabel='Projects documentation'
|
||||||
formatApiCode={formatApiCode}
|
formatApiCode={formatApiCode}
|
||||||
>
|
>
|
||||||
<ProjectForm
|
<ProjectForm
|
||||||
@ -113,12 +111,12 @@ const CreateProject = () => {
|
|||||||
setProjectName={setProjectName}
|
setProjectName={setProjectName}
|
||||||
projectDesc={projectDesc}
|
projectDesc={projectDesc}
|
||||||
setProjectDesc={setProjectDesc}
|
setProjectDesc={setProjectDesc}
|
||||||
mode="Create"
|
mode='Create'
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
validateProjectId={validateProjectId}
|
validateProjectId={validateProjectId}
|
||||||
>
|
>
|
||||||
<CreateButton
|
<CreateButton
|
||||||
name="project"
|
name='project'
|
||||||
permission={CREATE_PROJECT}
|
permission={CREATE_PROJECT}
|
||||||
data-testid={CREATE_PROJECT_BTN}
|
data-testid={CREATE_PROJECT_BTN}
|
||||||
/>
|
/>
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import { Box, styled, Typography } from "@mui/material";
|
import { Box, styled, Typography } from '@mui/material';
|
||||||
import { FC } from "react";
|
import { FC } from 'react';
|
||||||
import { HelpIcon } from "component/common/HelpIcon/HelpIcon";
|
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
||||||
import { useUiFlag } from "hooks/useUiFlag";
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
import { ConditionallyRender } from "component/common/ConditionallyRender/ConditionallyRender";
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
const StyledTitle = styled(Typography)(({ theme }) => ({
|
const StyledTitle = styled(Typography)(({ theme }) => ({
|
||||||
fontWeight: theme.fontWeight.bold,
|
fontWeight: theme.fontWeight.bold,
|
||||||
display: "inline",
|
display: 'inline',
|
||||||
}));
|
}));
|
||||||
const StyledDescription = styled(Typography)(({ theme }) => ({
|
const StyledDescription = styled(Typography)(({ theme }) => ({
|
||||||
display: "inline",
|
display: 'inline',
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const CollaborationModeTooltip: FC = () => {
|
export const CollaborationModeTooltip: FC = () => {
|
||||||
const privateProjects = useUiFlag("privateProjects");
|
const privateProjects = useUiFlag('privateProjects');
|
||||||
return (
|
return (
|
||||||
<HelpIcon
|
<HelpIcon
|
||||||
htmlTooltip
|
htmlTooltip
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Box } from "@mui/material";
|
import { Box } from '@mui/material';
|
||||||
import { FC } from "react";
|
import { FC } from 'react';
|
||||||
import { HelpIcon } from "component/common/HelpIcon/HelpIcon";
|
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
||||||
|
|
||||||
export const FeatureFlagNamingTooltip: FC = () => {
|
export const FeatureFlagNamingTooltip: FC = () => {
|
||||||
return (
|
return (
|
||||||
@ -9,8 +9,8 @@ export const FeatureFlagNamingTooltip: FC = () => {
|
|||||||
tooltip={
|
tooltip={
|
||||||
<Box>
|
<Box>
|
||||||
<p>
|
<p>
|
||||||
For example, the pattern{" "}
|
For example, the pattern{' '}
|
||||||
<code>{"[a-z0-9]{2}\\.[a-z]{4,12}"}</code> matches
|
<code>{'[a-z0-9]{2}\\.[a-z]{4,12}'}</code> matches
|
||||||
'a1.project', but not 'a1.project.feature-1'.
|
'a1.project', but not 'a1.project.feature-1'.
|
||||||
</p>
|
</p>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from 'react';
|
||||||
import { ConditionallyRender } from "component/common/ConditionallyRender/ConditionallyRender";
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import Select from "component/common/select";
|
import Select from 'component/common/select';
|
||||||
import { ProjectMode } from "../hooks/useProjectEnterpriseSettingsForm";
|
import { ProjectMode } from '../hooks/useProjectEnterpriseSettingsForm';
|
||||||
import { Box, InputAdornment, styled, TextField } from "@mui/material";
|
import { Box, InputAdornment, styled, TextField } from '@mui/material';
|
||||||
import { CollaborationModeTooltip } from "./CollaborationModeTooltip";
|
import { CollaborationModeTooltip } from './CollaborationModeTooltip';
|
||||||
import Input from "component/common/Input/Input";
|
import Input from 'component/common/Input/Input';
|
||||||
import { FeatureFlagNamingTooltip } from "./FeatureFlagNamingTooltip";
|
import { FeatureFlagNamingTooltip } from './FeatureFlagNamingTooltip';
|
||||||
import { usePlausibleTracker } from "hooks/usePlausibleTracker";
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
import { useUiFlag } from "hooks/useUiFlag";
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
|
||||||
interface IProjectEnterpriseSettingsForm {
|
interface IProjectEnterpriseSettingsForm {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -24,12 +24,12 @@ interface IProjectEnterpriseSettingsForm {
|
|||||||
clearErrors: () => void;
|
clearErrors: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledForm = styled("form")(({ theme }) => ({
|
const StyledForm = styled('form')(({ theme }) => ({
|
||||||
height: "100%",
|
height: '100%',
|
||||||
paddingBottom: theme.spacing(4),
|
paddingBottom: theme.spacing(4),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledSubtitle = styled("div")(({ theme }) => ({
|
const StyledSubtitle = styled('div')(({ theme }) => ({
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
fontSize: theme.fontSizes.smallerBody,
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
lineHeight: 1.25,
|
lineHeight: 1.25,
|
||||||
@ -37,42 +37,42 @@ const StyledSubtitle = styled("div")(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledInput = styled(Input)(({ theme }) => ({
|
const StyledInput = styled(Input)(({ theme }) => ({
|
||||||
width: "100%",
|
width: '100%',
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
paddingRight: theme.spacing(1),
|
paddingRight: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledTextField = styled(TextField)(({ theme }) => ({
|
const StyledTextField = styled(TextField)(({ theme }) => ({
|
||||||
width: "100%",
|
width: '100%',
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledFieldset = styled("fieldset")(() => ({
|
const StyledFieldset = styled('fieldset')(() => ({
|
||||||
padding: 0,
|
padding: 0,
|
||||||
border: "none",
|
border: 'none',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledSelect = styled(Select)(({ theme }) => ({
|
const StyledSelect = styled(Select)(({ theme }) => ({
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
minWidth: "200px",
|
minWidth: '200px',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledButtonContainer = styled("div")(() => ({
|
const StyledButtonContainer = styled('div')(() => ({
|
||||||
marginTop: "auto",
|
marginTop: 'auto',
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
justifyContent: "flex-end",
|
justifyContent: 'flex-end',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledFlagNamingContainer = styled("div")(({ theme }) => ({
|
const StyledFlagNamingContainer = styled('div')(({ theme }) => ({
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
alignItems: "flex-start",
|
alignItems: 'flex-start',
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
"& > *": { width: "100%" },
|
'& > *': { width: '100%' },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledPatternNamingExplanation = styled("div")(({ theme }) => ({
|
const StyledPatternNamingExplanation = styled('div')(({ theme }) => ({
|
||||||
"p + p": { marginTop: theme.spacing(1) },
|
'p + p': { marginTop: theme.spacing(1) },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const validateFeatureNamingExample = ({
|
export const validateFeatureNamingExample = ({
|
||||||
@ -83,285 +83,288 @@ export const validateFeatureNamingExample = ({
|
|||||||
pattern: string;
|
pattern: string;
|
||||||
example: string;
|
example: string;
|
||||||
featureNamingPatternError: string | undefined;
|
featureNamingPatternError: string | undefined;
|
||||||
}): { state: "valid" } | { state: "invalid"; reason: string } => {
|
}): { state: 'valid' } | { state: 'invalid'; reason: string } => {
|
||||||
if (featureNamingPatternError || !example || !pattern) {
|
if (featureNamingPatternError || !example || !pattern) {
|
||||||
return { state: "valid" };
|
return { state: 'valid' };
|
||||||
} else if (example && pattern) {
|
} else if (example && pattern) {
|
||||||
const regex = new RegExp(`^${pattern}$`);
|
const regex = new RegExp(`^${pattern}$`);
|
||||||
const matches = regex.test(example);
|
const matches = regex.test(example);
|
||||||
if (!matches) {
|
if (!matches) {
|
||||||
return { state: "invalid", reason: "Example does not match regex" };
|
return { state: 'invalid', reason: 'Example does not match regex' };
|
||||||
} else {
|
} else {
|
||||||
return { state: "valid" };
|
return { state: 'valid' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { state: "valid" };
|
return { state: 'valid' };
|
||||||
};
|
};
|
||||||
|
|
||||||
const useFeatureNamePatternTracking = () => {
|
const useFeatureNamePatternTracking = () => {
|
||||||
const [previousPattern, setPreviousPattern] = React.useState<string>("");
|
const [previousPattern, setPreviousPattern] = React.useState<string>('');
|
||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
const eventName = "feature-naming-pattern" as const;
|
const eventName = 'feature-naming-pattern' as const;
|
||||||
|
|
||||||
const trackPattern = (pattern: string = "") => {
|
const trackPattern = (pattern: string = '') => {
|
||||||
if (pattern === previousPattern) {
|
if (pattern === previousPattern) {
|
||||||
// do nothing; they've probably updated something else in the
|
// do nothing; they've probably updated something else in the
|
||||||
// project.
|
// project.
|
||||||
} else if (pattern === "" && previousPattern !== "") {
|
} else if (pattern === '' && previousPattern !== '') {
|
||||||
trackEvent(eventName, { props: { action: "removed" } });
|
trackEvent(eventName, { props: { action: 'removed' } });
|
||||||
} else if (pattern !== "" && previousPattern === "") {
|
} else if (pattern !== '' && previousPattern === '') {
|
||||||
trackEvent(eventName, { props: { action: "added" } });
|
trackEvent(eventName, { props: { action: 'added' } });
|
||||||
} else if (pattern !== "" && previousPattern !== "") {
|
} else if (pattern !== '' && previousPattern !== '') {
|
||||||
trackEvent(eventName, { props: { action: "edited" } });
|
trackEvent(eventName, { props: { action: 'edited' } });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { trackPattern, setPreviousPattern };
|
return { trackPattern, setPreviousPattern };
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectEnterpriseSettingsForm: React.FC<
|
const ProjectEnterpriseSettingsForm: React.FC<IProjectEnterpriseSettingsForm> =
|
||||||
IProjectEnterpriseSettingsForm
|
({
|
||||||
> = ({
|
children,
|
||||||
children,
|
handleSubmit,
|
||||||
handleSubmit,
|
projectId,
|
||||||
projectId,
|
projectMode,
|
||||||
projectMode,
|
featureNamingExample,
|
||||||
featureNamingExample,
|
featureNamingPattern,
|
||||||
featureNamingPattern,
|
featureNamingDescription,
|
||||||
featureNamingDescription,
|
setFeatureNamingExample,
|
||||||
setFeatureNamingExample,
|
setFeatureNamingPattern,
|
||||||
setFeatureNamingPattern,
|
setFeatureNamingDescription,
|
||||||
setFeatureNamingDescription,
|
setProjectMode,
|
||||||
setProjectMode,
|
errors,
|
||||||
errors,
|
clearErrors,
|
||||||
clearErrors,
|
|
||||||
}) => {
|
|
||||||
const privateProjects = useUiFlag("privateProjects");
|
|
||||||
const shouldShowFlagNaming = useUiFlag("featureNamingPattern");
|
|
||||||
|
|
||||||
const { setPreviousPattern, trackPattern } =
|
|
||||||
useFeatureNamePatternTracking();
|
|
||||||
|
|
||||||
const projectModeOptions = privateProjects
|
|
||||||
? [
|
|
||||||
{ key: "open", label: "open" },
|
|
||||||
{ key: "protected", label: "protected" },
|
|
||||||
{ key: "private", label: "private" },
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{ key: "open", label: "open" },
|
|
||||||
{ key: "protected", label: "protected" },
|
|
||||||
];
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setPreviousPattern(featureNamingPattern || "");
|
|
||||||
}, [projectId]);
|
|
||||||
|
|
||||||
const updateNamingExampleError = ({
|
|
||||||
example,
|
|
||||||
pattern,
|
|
||||||
}: {
|
|
||||||
example: string;
|
|
||||||
pattern: string;
|
|
||||||
}) => {
|
}) => {
|
||||||
const validationResult = validateFeatureNamingExample({
|
const privateProjects = useUiFlag('privateProjects');
|
||||||
|
const shouldShowFlagNaming = useUiFlag('featureNamingPattern');
|
||||||
|
|
||||||
|
const { setPreviousPattern, trackPattern } =
|
||||||
|
useFeatureNamePatternTracking();
|
||||||
|
|
||||||
|
const projectModeOptions = privateProjects
|
||||||
|
? [
|
||||||
|
{ key: 'open', label: 'open' },
|
||||||
|
{ key: 'protected', label: 'protected' },
|
||||||
|
{ key: 'private', label: 'private' },
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{ key: 'open', label: 'open' },
|
||||||
|
{ key: 'protected', label: 'protected' },
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPreviousPattern(featureNamingPattern || '');
|
||||||
|
}, [projectId]);
|
||||||
|
|
||||||
|
const updateNamingExampleError = ({
|
||||||
|
example,
|
||||||
pattern,
|
pattern,
|
||||||
example,
|
}: {
|
||||||
featureNamingPatternError: errors.featureNamingPattern,
|
example: string;
|
||||||
});
|
pattern: string;
|
||||||
|
}) => {
|
||||||
|
const validationResult = validateFeatureNamingExample({
|
||||||
|
pattern,
|
||||||
|
example,
|
||||||
|
featureNamingPatternError: errors.featureNamingPattern,
|
||||||
|
});
|
||||||
|
|
||||||
switch (validationResult.state) {
|
switch (validationResult.state) {
|
||||||
case "invalid":
|
case 'invalid':
|
||||||
errors.namingExample = validationResult.reason;
|
errors.namingExample = validationResult.reason;
|
||||||
break;
|
break;
|
||||||
case "valid":
|
case 'valid':
|
||||||
delete errors.namingExample;
|
delete errors.namingExample;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSetFeatureNamingPattern = (regex: string) => {
|
|
||||||
const disallowedStrings = [
|
|
||||||
" ",
|
|
||||||
"\\t",
|
|
||||||
"\\s",
|
|
||||||
"\\n",
|
|
||||||
"\\r",
|
|
||||||
"\\f",
|
|
||||||
"\\v",
|
|
||||||
];
|
|
||||||
if (
|
|
||||||
disallowedStrings.some((blockedString) =>
|
|
||||||
regex.includes(blockedString)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
errors.featureNamingPattern =
|
|
||||||
"Whitespace is not allowed in the expression";
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
new RegExp(regex);
|
|
||||||
delete errors.featureNamingPattern;
|
|
||||||
} catch (e) {
|
|
||||||
errors.featureNamingPattern = "Invalid regular expression";
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
setFeatureNamingPattern?.(regex);
|
|
||||||
updateNamingExampleError({
|
|
||||||
pattern: regex,
|
|
||||||
example: featureNamingExample || "",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSetFeatureNamingExample = (example: string) => {
|
const onSetFeatureNamingPattern = (regex: string) => {
|
||||||
setFeatureNamingExample && setFeatureNamingExample(example);
|
const disallowedStrings = [
|
||||||
updateNamingExampleError({
|
' ',
|
||||||
pattern: featureNamingPattern || "",
|
'\\t',
|
||||||
example,
|
'\\s',
|
||||||
});
|
'\\n',
|
||||||
};
|
'\\r',
|
||||||
|
'\\f',
|
||||||
|
'\\v',
|
||||||
|
];
|
||||||
|
if (
|
||||||
|
disallowedStrings.some((blockedString) =>
|
||||||
|
regex.includes(blockedString),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
errors.featureNamingPattern =
|
||||||
|
'Whitespace is not allowed in the expression';
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
new RegExp(regex);
|
||||||
|
delete errors.featureNamingPattern;
|
||||||
|
} catch (e) {
|
||||||
|
errors.featureNamingPattern = 'Invalid regular expression';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFeatureNamingPattern?.(regex);
|
||||||
|
updateNamingExampleError({
|
||||||
|
pattern: regex,
|
||||||
|
example: featureNamingExample || '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onSetFeatureNamingDescription = (description: string) => {
|
const onSetFeatureNamingExample = (example: string) => {
|
||||||
setFeatureNamingDescription?.(description);
|
setFeatureNamingExample && setFeatureNamingExample(example);
|
||||||
};
|
updateNamingExampleError({
|
||||||
|
pattern: featureNamingPattern || '',
|
||||||
|
example,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
const onSetFeatureNamingDescription = (description: string) => {
|
||||||
<StyledForm
|
setFeatureNamingDescription?.(description);
|
||||||
onSubmit={(submitEvent) => {
|
};
|
||||||
handleSubmit(submitEvent);
|
|
||||||
trackPattern(featureNamingPattern);
|
return (
|
||||||
}}
|
<StyledForm
|
||||||
>
|
onSubmit={(submitEvent) => {
|
||||||
<>
|
handleSubmit(submitEvent);
|
||||||
<Box
|
trackPattern(featureNamingPattern);
|
||||||
sx={{
|
}}
|
||||||
display: "flex",
|
>
|
||||||
alignItems: "center",
|
<>
|
||||||
marginBottom: 1,
|
<Box
|
||||||
gap: 1,
|
sx={{
|
||||||
}}
|
display: 'flex',
|
||||||
>
|
alignItems: 'center',
|
||||||
<p>What is your project collaboration mode?</p>
|
marginBottom: 1,
|
||||||
<CollaborationModeTooltip />
|
gap: 1,
|
||||||
</Box>
|
}}
|
||||||
<StyledSelect
|
>
|
||||||
id="project-mode"
|
<p>What is your project collaboration mode?</p>
|
||||||
value={projectMode}
|
<CollaborationModeTooltip />
|
||||||
label="Project collaboration mode"
|
</Box>
|
||||||
name="Project collaboration mode"
|
<StyledSelect
|
||||||
onChange={(e) => {
|
id='project-mode'
|
||||||
setProjectMode?.(e.target.value as ProjectMode);
|
value={projectMode}
|
||||||
}}
|
label='Project collaboration mode'
|
||||||
options={projectModeOptions}
|
name='Project collaboration mode'
|
||||||
/>
|
onChange={(e) => {
|
||||||
</>
|
setProjectMode?.(e.target.value as ProjectMode);
|
||||||
<ConditionallyRender
|
}}
|
||||||
condition={Boolean(shouldShowFlagNaming)}
|
options={projectModeOptions}
|
||||||
show={
|
/>
|
||||||
<StyledFieldset>
|
</>
|
||||||
<Box
|
<ConditionallyRender
|
||||||
sx={{
|
condition={Boolean(shouldShowFlagNaming)}
|
||||||
display: "flex",
|
show={
|
||||||
alignItems: "center",
|
<StyledFieldset>
|
||||||
marginBottom: 1,
|
<Box
|
||||||
gap: 1,
|
sx={{
|
||||||
}}
|
display: 'flex',
|
||||||
>
|
alignItems: 'center',
|
||||||
<legend>Feature flag naming pattern?</legend>
|
marginBottom: 1,
|
||||||
<FeatureFlagNamingTooltip />
|
gap: 1,
|
||||||
</Box>
|
|
||||||
<StyledSubtitle>
|
|
||||||
<StyledPatternNamingExplanation id="pattern-naming-description">
|
|
||||||
<p>
|
|
||||||
Define a{" "}
|
|
||||||
<a
|
|
||||||
href={`https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Cheatsheet`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
JavaScript RegEx
|
|
||||||
</a>{" "}
|
|
||||||
used to enforce feature flag names within
|
|
||||||
this project. The regex will be surrounded
|
|
||||||
by a leading <code>^</code> and a trailing{" "}
|
|
||||||
<code>$</code>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Leave it empty if you don’t want to add a
|
|
||||||
naming pattern.
|
|
||||||
</p>
|
|
||||||
</StyledPatternNamingExplanation>
|
|
||||||
</StyledSubtitle>
|
|
||||||
<StyledFlagNamingContainer>
|
|
||||||
<StyledInput
|
|
||||||
label={"Naming Pattern"}
|
|
||||||
name="feature flag naming pattern"
|
|
||||||
aria-describedby="pattern-naming-description"
|
|
||||||
placeholder="[A-Za-z]+\.[A-Za-z]+\.[A-Za-z0-9-]+"
|
|
||||||
InputProps={{
|
|
||||||
startAdornment: (
|
|
||||||
<InputAdornment position="start">
|
|
||||||
^
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
$
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
type={"text"}
|
>
|
||||||
value={featureNamingPattern || ""}
|
<legend>Feature flag naming pattern?</legend>
|
||||||
error={Boolean(errors.featureNamingPattern)}
|
<FeatureFlagNamingTooltip />
|
||||||
errorText={errors.featureNamingPattern}
|
</Box>
|
||||||
onChange={(e) =>
|
|
||||||
onSetFeatureNamingPattern(e.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<StyledSubtitle>
|
<StyledSubtitle>
|
||||||
<p id="pattern-additional-description">
|
<StyledPatternNamingExplanation id='pattern-naming-description'>
|
||||||
The example and description will be shown to
|
<p>
|
||||||
users when they create a new feature flag in
|
Define a{' '}
|
||||||
this project.
|
<a
|
||||||
</p>
|
href={`https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Cheatsheet`}
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
JavaScript RegEx
|
||||||
|
</a>{' '}
|
||||||
|
used to enforce feature flag names
|
||||||
|
within this project. The regex will be
|
||||||
|
surrounded by a leading <code>^</code>{' '}
|
||||||
|
and a trailing <code>$</code>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Leave it empty if you don’t want to add
|
||||||
|
a naming pattern.
|
||||||
|
</p>
|
||||||
|
</StyledPatternNamingExplanation>
|
||||||
</StyledSubtitle>
|
</StyledSubtitle>
|
||||||
|
<StyledFlagNamingContainer>
|
||||||
|
<StyledInput
|
||||||
|
label={'Naming Pattern'}
|
||||||
|
name='feature flag naming pattern'
|
||||||
|
aria-describedby='pattern-naming-description'
|
||||||
|
placeholder='[A-Za-z]+.[A-Za-z]+.[A-Za-z0-9-]+'
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position='start'>
|
||||||
|
^
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position='end'>
|
||||||
|
$
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
type={'text'}
|
||||||
|
value={featureNamingPattern || ''}
|
||||||
|
error={Boolean(errors.featureNamingPattern)}
|
||||||
|
errorText={errors.featureNamingPattern}
|
||||||
|
onChange={(e) =>
|
||||||
|
onSetFeatureNamingPattern(
|
||||||
|
e.target.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledSubtitle>
|
||||||
|
<p id='pattern-additional-description'>
|
||||||
|
The example and description will be
|
||||||
|
shown to users when they create a new
|
||||||
|
feature flag in this project.
|
||||||
|
</p>
|
||||||
|
</StyledSubtitle>
|
||||||
|
|
||||||
<StyledInput
|
<StyledInput
|
||||||
label={"Naming Example"}
|
label={'Naming Example'}
|
||||||
name="feature flag naming example"
|
name='feature flag naming example'
|
||||||
type={"text"}
|
type={'text'}
|
||||||
aria-describedby="pattern-additional-description"
|
aria-describedby='pattern-additional-description'
|
||||||
value={featureNamingExample || ""}
|
value={featureNamingExample || ''}
|
||||||
placeholder="dx.feature1.1-135"
|
placeholder='dx.feature1.1-135'
|
||||||
error={Boolean(errors.namingExample)}
|
error={Boolean(errors.namingExample)}
|
||||||
errorText={errors.namingExample}
|
errorText={errors.namingExample}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onSetFeatureNamingExample(e.target.value)
|
onSetFeatureNamingExample(
|
||||||
}
|
e.target.value,
|
||||||
/>
|
)
|
||||||
<StyledTextField
|
}
|
||||||
label={"Naming pattern description"}
|
/>
|
||||||
name="feature flag naming description"
|
<StyledTextField
|
||||||
type={"text"}
|
label={'Naming pattern description'}
|
||||||
aria-describedby="pattern-additional-description"
|
name='feature flag naming description'
|
||||||
placeholder={`<project>.<featureName>.<ticket>
|
type={'text'}
|
||||||
|
aria-describedby='pattern-additional-description'
|
||||||
|
placeholder={`<project>.<featureName>.<ticket>
|
||||||
|
|
||||||
The flag name should contain the project name, the feature name, and the ticket number, each separated by a dot.`}
|
The flag name should contain the project name, the feature name, and the ticket number, each separated by a dot.`}
|
||||||
multiline
|
multiline
|
||||||
minRows={5}
|
minRows={5}
|
||||||
value={featureNamingDescription || ""}
|
value={featureNamingDescription || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onSetFeatureNamingDescription(
|
onSetFeatureNamingDescription(
|
||||||
e.target.value
|
e.target.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</StyledFlagNamingContainer>
|
</StyledFlagNamingContainer>
|
||||||
</StyledFieldset>
|
</StyledFieldset>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StyledButtonContainer>{children}</StyledButtonContainer>
|
<StyledButtonContainer>{children}</StyledButtonContainer>
|
||||||
</StyledForm>
|
</StyledForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectEnterpriseSettingsForm;
|
export default ProjectEnterpriseSettingsForm;
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { trim } from "component/common/util";
|
import { trim } from 'component/common/util';
|
||||||
import { StickinessSelect } from "component/feature/StrategyTypes/FlexibleStrategy/StickinessSelect/StickinessSelect";
|
import { StickinessSelect } from 'component/feature/StrategyTypes/FlexibleStrategy/StickinessSelect/StickinessSelect';
|
||||||
import { ConditionallyRender } from "component/common/ConditionallyRender/ConditionallyRender";
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { Box, styled, TextField } from "@mui/material";
|
import { Box, styled, TextField } from '@mui/material';
|
||||||
import Input from "component/common/Input/Input";
|
import Input from 'component/common/Input/Input';
|
||||||
import { FeatureTogglesLimitTooltip } from "./FeatureTogglesLimitTooltip";
|
import { FeatureTogglesLimitTooltip } from './FeatureTogglesLimitTooltip';
|
||||||
import { ProjectMode } from "../hooks/useProjectEnterpriseSettingsForm";
|
import { ProjectMode } from '../hooks/useProjectEnterpriseSettingsForm';
|
||||||
import useUiConfig from "hooks/api/getters/useUiConfig/useUiConfig";
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { CollaborationModeTooltip } from "../ProjectEnterpriseSettingsForm/CollaborationModeTooltip";
|
import { CollaborationModeTooltip } from '../ProjectEnterpriseSettingsForm/CollaborationModeTooltip';
|
||||||
import Select from "component/common/select";
|
import Select from 'component/common/select';
|
||||||
import { useUiFlag } from "hooks/useUiFlag";
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
|
||||||
interface IProjectForm {
|
interface IProjectForm {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -27,32 +27,32 @@ interface IProjectForm {
|
|||||||
setProjectMode?: React.Dispatch<React.SetStateAction<ProjectMode>>;
|
setProjectMode?: React.Dispatch<React.SetStateAction<ProjectMode>>;
|
||||||
handleSubmit: (e: any) => void;
|
handleSubmit: (e: any) => void;
|
||||||
errors: { [key: string]: string };
|
errors: { [key: string]: string };
|
||||||
mode: "Create" | "Edit";
|
mode: 'Create' | 'Edit';
|
||||||
clearErrors: () => void;
|
clearErrors: () => void;
|
||||||
validateProjectId: () => void;
|
validateProjectId: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PROJECT_STICKINESS_SELECT = "PROJECT_STICKINESS_SELECT";
|
const PROJECT_STICKINESS_SELECT = 'PROJECT_STICKINESS_SELECT';
|
||||||
const PROJECT_ID_INPUT = "PROJECT_ID_INPUT";
|
const PROJECT_ID_INPUT = 'PROJECT_ID_INPUT';
|
||||||
const PROJECT_NAME_INPUT = "PROJECT_NAME_INPUT";
|
const PROJECT_NAME_INPUT = 'PROJECT_NAME_INPUT';
|
||||||
const PROJECT_DESCRIPTION_INPUT = "PROJECT_DESCRIPTION_INPUT";
|
const PROJECT_DESCRIPTION_INPUT = 'PROJECT_DESCRIPTION_INPUT';
|
||||||
|
|
||||||
const StyledForm = styled("form")(({ theme }) => ({
|
const StyledForm = styled('form')(({ theme }) => ({
|
||||||
height: "100%",
|
height: '100%',
|
||||||
paddingBottom: theme.spacing(1),
|
paddingBottom: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledDescription = styled("p")(({ theme }) => ({
|
const StyledDescription = styled('p')(({ theme }) => ({
|
||||||
marginBottom: theme.spacing(1),
|
marginBottom: theme.spacing(1),
|
||||||
marginRight: theme.spacing(1),
|
marginRight: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledSelect = styled(Select)(({ theme }) => ({
|
const StyledSelect = styled(Select)(({ theme }) => ({
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
minWidth: "200px",
|
minWidth: '200px',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledSubtitle = styled("div")(({ theme }) => ({
|
const StyledSubtitle = styled('div')(({ theme }) => ({
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
fontSize: theme.fontSizes.smallerBody,
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
lineHeight: 1.25,
|
lineHeight: 1.25,
|
||||||
@ -60,25 +60,25 @@ const StyledSubtitle = styled("div")(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledInput = styled(Input)(({ theme }) => ({
|
const StyledInput = styled(Input)(({ theme }) => ({
|
||||||
width: "100%",
|
width: '100%',
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
paddingRight: theme.spacing(1),
|
paddingRight: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledTextField = styled(TextField)(({ theme }) => ({
|
const StyledTextField = styled(TextField)(({ theme }) => ({
|
||||||
width: "100%",
|
width: '100%',
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledButtonContainer = styled("div")(() => ({
|
const StyledButtonContainer = styled('div')(() => ({
|
||||||
marginTop: "auto",
|
marginTop: 'auto',
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
justifyContent: "flex-end",
|
justifyContent: 'flex-end',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledInputContainer = styled("div")(() => ({
|
const StyledInputContainer = styled('div')(() => ({
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const ProjectForm: React.FC<IProjectForm> = ({
|
const ProjectForm: React.FC<IProjectForm> = ({
|
||||||
@ -103,17 +103,17 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
clearErrors,
|
clearErrors,
|
||||||
}) => {
|
}) => {
|
||||||
const { isEnterprise } = useUiConfig();
|
const { isEnterprise } = useUiConfig();
|
||||||
const privateProjects = useUiFlag("privateProjects");
|
const privateProjects = useUiFlag('privateProjects');
|
||||||
|
|
||||||
const projectModeOptions = privateProjects
|
const projectModeOptions = privateProjects
|
||||||
? [
|
? [
|
||||||
{ key: "open", label: "open" },
|
{ key: 'open', label: 'open' },
|
||||||
{ key: "protected", label: "protected" },
|
{ key: 'protected', label: 'protected' },
|
||||||
{ key: "private", label: "private" },
|
{ key: 'private', label: 'private' },
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ key: "open", label: "open" },
|
{ key: 'open', label: 'open' },
|
||||||
{ key: "protected", label: "protected" },
|
{ key: 'protected', label: 'protected' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -124,14 +124,14 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
>
|
>
|
||||||
<StyledDescription>What is your project Id?</StyledDescription>
|
<StyledDescription>What is your project Id?</StyledDescription>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
label="Project Id"
|
label='Project Id'
|
||||||
value={projectId}
|
value={projectId}
|
||||||
onChange={(e) => setProjectId(trim(e.target.value))}
|
onChange={(e) => setProjectId(trim(e.target.value))}
|
||||||
error={Boolean(errors.id)}
|
error={Boolean(errors.id)}
|
||||||
errorText={errors.id}
|
errorText={errors.id}
|
||||||
onFocus={() => clearErrors()}
|
onFocus={() => clearErrors()}
|
||||||
onBlur={validateProjectId}
|
onBlur={validateProjectId}
|
||||||
disabled={mode === "Edit"}
|
disabled={mode === 'Edit'}
|
||||||
data-testid={PROJECT_ID_INPUT}
|
data-testid={PROJECT_ID_INPUT}
|
||||||
autoFocus
|
autoFocus
|
||||||
required
|
required
|
||||||
@ -139,7 +139,7 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
|
|
||||||
<StyledDescription>What is your project name?</StyledDescription>
|
<StyledDescription>What is your project name?</StyledDescription>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
label="Project name"
|
label='Project name'
|
||||||
value={projectName}
|
value={projectName}
|
||||||
onChange={(e) => setProjectName(e.target.value)}
|
onChange={(e) => setProjectName(e.target.value)}
|
||||||
error={Boolean(errors.name)}
|
error={Boolean(errors.name)}
|
||||||
@ -155,8 +155,8 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
What is your project description?
|
What is your project description?
|
||||||
</StyledDescription>
|
</StyledDescription>
|
||||||
<StyledTextField
|
<StyledTextField
|
||||||
label="Project description"
|
label='Project description'
|
||||||
variant="outlined"
|
variant='outlined'
|
||||||
multiline
|
multiline
|
||||||
maxRows={4}
|
maxRows={4}
|
||||||
value={projectDesc}
|
value={projectDesc}
|
||||||
@ -172,7 +172,7 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
What is the default stickiness for the project?
|
What is the default stickiness for the project?
|
||||||
</StyledDescription>
|
</StyledDescription>
|
||||||
<StickinessSelect
|
<StickinessSelect
|
||||||
label="Stickiness"
|
label='Stickiness'
|
||||||
value={projectStickiness}
|
value={projectStickiness}
|
||||||
data-testid={PROJECT_STICKINESS_SELECT}
|
data-testid={PROJECT_STICKINESS_SELECT}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@ -184,13 +184,13 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={mode === "Edit" && Boolean(setFeatureLimit)}
|
condition={mode === 'Edit' && Boolean(setFeatureLimit)}
|
||||||
show={
|
show={
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
marginBottom: 1,
|
marginBottom: 1,
|
||||||
gap: 1,
|
gap: 1,
|
||||||
}}
|
}}
|
||||||
@ -204,9 +204,9 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
<StyledInputContainer>
|
<StyledInputContainer>
|
||||||
{featureLimit && setFeatureLimit && (
|
{featureLimit && setFeatureLimit && (
|
||||||
<StyledInput
|
<StyledInput
|
||||||
label={"Limit"}
|
label={'Limit'}
|
||||||
name="value"
|
name='value'
|
||||||
type={"number"}
|
type={'number'}
|
||||||
value={featureLimit}
|
value={featureLimit}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFeatureLimit(e.target.value)
|
setFeatureLimit(e.target.value)
|
||||||
@ -229,13 +229,13 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={mode === "Create" && isEnterprise()}
|
condition={mode === 'Create' && isEnterprise()}
|
||||||
show={
|
show={
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
marginBottom: 1,
|
marginBottom: 1,
|
||||||
gap: 1,
|
gap: 1,
|
||||||
}}
|
}}
|
||||||
@ -244,10 +244,10 @@ const ProjectForm: React.FC<IProjectForm> = ({
|
|||||||
<CollaborationModeTooltip />
|
<CollaborationModeTooltip />
|
||||||
</Box>
|
</Box>
|
||||||
<StyledSelect
|
<StyledSelect
|
||||||
id="project-mode"
|
id='project-mode'
|
||||||
value={projectMode}
|
value={projectMode}
|
||||||
label="Project collaboration mode"
|
label='Project collaboration mode'
|
||||||
name="Project collaboration mode"
|
name='Project collaboration mode'
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setProjectMode?.(e.target.value as ProjectMode);
|
setProjectMode?.(e.target.value as ProjectMode);
|
||||||
}}
|
}}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { DeleteProject } from "../DeleteProject";
|
import { DeleteProject } from '../DeleteProject';
|
||||||
import FormTemplate from "component/common/FormTemplate/FormTemplate";
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import { useRequiredPathParam } from "hooks/useRequiredPathParam";
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import useProjectApi from "hooks/api/actions/useProjectApi/useProjectApi";
|
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
||||||
import useUiConfig from "hooks/api/getters/useUiConfig/useUiConfig";
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
|
||||||
interface IDeleteProjectForm {
|
interface IDeleteProjectForm {
|
||||||
featureCount: number;
|
featureCount: number;
|
||||||
}
|
}
|
||||||
export const DeleteProjectForm = ({ featureCount }: IDeleteProjectForm) => {
|
export const DeleteProjectForm = ({ featureCount }: IDeleteProjectForm) => {
|
||||||
const id = useRequiredPathParam("projectId");
|
const id = useRequiredPathParam('projectId');
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const { loading } = useProjectApi();
|
const { loading } = useProjectApi();
|
||||||
const formatProjectDeleteApiCode = () => {
|
const formatProjectDeleteApiCode = () => {
|
||||||
@ -20,10 +20,10 @@ export const DeleteProjectForm = ({ featureCount }: IDeleteProjectForm) => {
|
|||||||
return (
|
return (
|
||||||
<FormTemplate
|
<FormTemplate
|
||||||
loading={loading}
|
loading={loading}
|
||||||
title="Delete Project"
|
title='Delete Project'
|
||||||
description=""
|
description=''
|
||||||
documentationLink="https://docs.getunleash.io/reference/projects"
|
documentationLink='https://docs.getunleash.io/reference/projects'
|
||||||
documentationLinkLabel="Projects documentation"
|
documentationLinkLabel='Projects documentation'
|
||||||
formatApiCode={formatProjectDeleteApiCode}
|
formatApiCode={formatProjectDeleteApiCode}
|
||||||
compact
|
compact
|
||||||
compactPadding
|
compactPadding
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
import { UPDATE_PROJECT } from "component/providers/AccessProvider/permissions";
|
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
||||||
import useProject from "hooks/api/getters/useProject/useProject";
|
import useProject from 'hooks/api/getters/useProject/useProject';
|
||||||
import useUiConfig from "hooks/api/getters/useUiConfig/useUiConfig";
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { useRequiredPathParam } from "hooks/useRequiredPathParam";
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import React, { useContext } from "react";
|
import React, { useContext } from 'react';
|
||||||
import AccessContext from "contexts/AccessContext";
|
import AccessContext from 'contexts/AccessContext';
|
||||||
import { Alert, styled } from "@mui/material";
|
import { Alert, styled } from '@mui/material';
|
||||||
import { ConditionallyRender } from "component/common/ConditionallyRender/ConditionallyRender";
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { UpdateEnterpriseSettings } from "./UpdateEnterpriseSettings";
|
import { UpdateEnterpriseSettings } from './UpdateEnterpriseSettings';
|
||||||
import { UpdateProject } from "./UpdateProject";
|
import { UpdateProject } from './UpdateProject';
|
||||||
import { DeleteProjectForm } from "./DeleteProjectForm";
|
import { DeleteProjectForm } from './DeleteProjectForm';
|
||||||
|
|
||||||
const StyledFormContainer = styled("div")(({ theme }) => ({
|
const StyledFormContainer = styled('div')(({ theme }) => ({
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const EditProject = () => {
|
const EditProject = () => {
|
||||||
const { isEnterprise } = useUiConfig();
|
const { isEnterprise } = useUiConfig();
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
const id = useRequiredPathParam("projectId");
|
const id = useRequiredPathParam('projectId');
|
||||||
const { project } = useProject(id);
|
const { project } = useProject(id);
|
||||||
|
|
||||||
const accessDeniedAlert = !hasAccess(UPDATE_PROJECT, id) && (
|
const accessDeniedAlert = !hasAccess(UPDATE_PROJECT, id) && (
|
||||||
<Alert severity="error" sx={{ mb: 4 }}>
|
<Alert severity='error' sx={{ mb: 4 }}>
|
||||||
You do not have the required permissions to edit this project.
|
You do not have the required permissions to edit this project.
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from 'react';
|
||||||
import useUiConfig from "hooks/api/getters/useUiConfig/useUiConfig";
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import useToast from "hooks/useToast";
|
import useToast from 'hooks/useToast';
|
||||||
import { useRequiredPathParam } from "hooks/useRequiredPathParam";
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import useProjectEnterpriseSettingsForm from "component/project/Project/hooks/useProjectEnterpriseSettingsForm";
|
import useProjectEnterpriseSettingsForm from 'component/project/Project/hooks/useProjectEnterpriseSettingsForm';
|
||||||
import useProject from "hooks/api/getters/useProject/useProject";
|
import useProject from 'hooks/api/getters/useProject/useProject';
|
||||||
import useProjectApi from "hooks/api/actions/useProjectApi/useProjectApi";
|
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
||||||
import { formatUnknownError } from "utils/formatUnknownError";
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import FormTemplate from "component/common/FormTemplate/FormTemplate";
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import ProjectEnterpriseSettingsForm from "component/project/Project/ProjectEnterpriseSettingsForm/ProjectEnterpriseSettingsForm";
|
import ProjectEnterpriseSettingsForm from 'component/project/Project/ProjectEnterpriseSettingsForm/ProjectEnterpriseSettingsForm';
|
||||||
import PermissionButton from "component/common/PermissionButton/PermissionButton";
|
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||||
import { UPDATE_PROJECT } from "component/providers/AccessProvider/permissions";
|
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
||||||
import { IProject } from "component/../interfaces/project";
|
import { IProject } from 'component/../interfaces/project';
|
||||||
import { styled } from "@mui/material";
|
import { styled } from '@mui/material';
|
||||||
import { usePlausibleTracker } from "hooks/usePlausibleTracker";
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
|
||||||
const StyledContainer = styled("div")(({ theme }) => ({
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
borderRadius: theme.spacing(2),
|
borderRadius: theme.spacing(2),
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
width: "100%",
|
width: '100%',
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
margin: "0 auto",
|
margin: '0 auto',
|
||||||
overflow: "hidden",
|
overflow: 'hidden',
|
||||||
[theme.breakpoints.down(1100)]: {
|
[theme.breakpoints.down(1100)]: {
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledFormContainer = styled("div")(({ theme }) => ({
|
const StyledFormContainer = styled('div')(({ theme }) => ({
|
||||||
borderTop: `1px solid ${theme.palette.divider}`,
|
borderTop: `1px solid ${theme.palette.divider}`,
|
||||||
paddingTop: theme.spacing(4),
|
paddingTop: theme.spacing(4),
|
||||||
}));
|
}));
|
||||||
@ -36,17 +36,17 @@ const StyledFormContainer = styled("div")(({ theme }) => ({
|
|||||||
interface IUpdateEnterpriseSettings {
|
interface IUpdateEnterpriseSettings {
|
||||||
project: IProject;
|
project: IProject;
|
||||||
}
|
}
|
||||||
const EDIT_PROJECT_SETTINGS_BTN = "EDIT_PROJECT_SETTINGS_BTN";
|
const EDIT_PROJECT_SETTINGS_BTN = 'EDIT_PROJECT_SETTINGS_BTN';
|
||||||
|
|
||||||
export const useModeTracking = () => {
|
export const useModeTracking = () => {
|
||||||
const [previousMode, setPreviousMode] = React.useState<string>("");
|
const [previousMode, setPreviousMode] = React.useState<string>('');
|
||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
const eventName = "project-mode" as const;
|
const eventName = 'project-mode' as const;
|
||||||
|
|
||||||
const trackModePattern = (newMode: string) => {
|
const trackModePattern = (newMode: string) => {
|
||||||
if (newMode !== previousMode) {
|
if (newMode !== previousMode) {
|
||||||
trackEvent(eventName, {
|
trackEvent(eventName, {
|
||||||
props: { mode: newMode, action: "updated" },
|
props: { mode: newMode, action: 'updated' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -59,7 +59,7 @@ export const UpdateEnterpriseSettings = ({
|
|||||||
}: IUpdateEnterpriseSettings) => {
|
}: IUpdateEnterpriseSettings) => {
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const id = useRequiredPathParam("projectId");
|
const id = useRequiredPathParam('projectId');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
projectMode,
|
projectMode,
|
||||||
@ -77,7 +77,7 @@ export const UpdateEnterpriseSettings = ({
|
|||||||
project.mode,
|
project.mode,
|
||||||
project?.featureNaming?.pattern,
|
project?.featureNaming?.pattern,
|
||||||
project?.featureNaming?.example,
|
project?.featureNaming?.example,
|
||||||
project?.featureNaming?.description
|
project?.featureNaming?.description,
|
||||||
);
|
);
|
||||||
|
|
||||||
const formatProjectSettingsApiCode = () => {
|
const formatProjectSettingsApiCode = () => {
|
||||||
@ -94,20 +94,20 @@ export const UpdateEnterpriseSettings = ({
|
|||||||
|
|
||||||
const useFeatureNamePatternTracking = () => {
|
const useFeatureNamePatternTracking = () => {
|
||||||
const [previousPattern, setPreviousPattern] =
|
const [previousPattern, setPreviousPattern] =
|
||||||
React.useState<string>("");
|
React.useState<string>('');
|
||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
const eventName = "feature-naming-pattern" as const;
|
const eventName = 'feature-naming-pattern' as const;
|
||||||
|
|
||||||
const trackPattern = (newPattern: string = "") => {
|
const trackPattern = (newPattern: string = '') => {
|
||||||
if (newPattern === previousPattern) {
|
if (newPattern === previousPattern) {
|
||||||
// do nothing; they've probably updated something else in the
|
// do nothing; they've probably updated something else in the
|
||||||
// project.
|
// project.
|
||||||
} else if (newPattern === "" && previousPattern !== "") {
|
} else if (newPattern === '' && previousPattern !== '') {
|
||||||
trackEvent(eventName, { props: { action: "removed" } });
|
trackEvent(eventName, { props: { action: 'removed' } });
|
||||||
} else if (newPattern !== "" && previousPattern === "") {
|
} else if (newPattern !== '' && previousPattern === '') {
|
||||||
trackEvent(eventName, { props: { action: "added" } });
|
trackEvent(eventName, { props: { action: 'added' } });
|
||||||
} else if (newPattern !== "" && previousPattern !== "") {
|
} else if (newPattern !== '' && previousPattern !== '') {
|
||||||
trackEvent(eventName, { props: { action: "edited" } });
|
trackEvent(eventName, { props: { action: 'edited' } });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,8 +126,8 @@ export const UpdateEnterpriseSettings = ({
|
|||||||
await editProjectSettings(id, payload);
|
await editProjectSettings(id, payload);
|
||||||
refetch();
|
refetch();
|
||||||
setToastData({
|
setToastData({
|
||||||
title: "Project information updated",
|
title: 'Project information updated',
|
||||||
type: "success",
|
type: 'success',
|
||||||
});
|
});
|
||||||
trackPattern(featureNamingPattern);
|
trackPattern(featureNamingPattern);
|
||||||
trackModePattern(projectMode);
|
trackModePattern(projectMode);
|
||||||
@ -137,7 +137,7 @@ export const UpdateEnterpriseSettings = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPreviousPattern(featureNamingPattern || "");
|
setPreviousPattern(featureNamingPattern || '');
|
||||||
setPreviousMode(projectMode);
|
setPreviousMode(projectMode);
|
||||||
}, [project]);
|
}, [project]);
|
||||||
|
|
||||||
@ -145,10 +145,10 @@ export const UpdateEnterpriseSettings = ({
|
|||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<FormTemplate
|
<FormTemplate
|
||||||
loading={loading}
|
loading={loading}
|
||||||
title="Enterprise Settings"
|
title='Enterprise Settings'
|
||||||
description=""
|
description=''
|
||||||
documentationLink="https://docs.getunleash.io/reference/projects"
|
documentationLink='https://docs.getunleash.io/reference/projects'
|
||||||
documentationLinkLabel="Projects documentation"
|
documentationLinkLabel='Projects documentation'
|
||||||
formatApiCode={formatProjectSettingsApiCode}
|
formatApiCode={formatProjectSettingsApiCode}
|
||||||
compactPadding
|
compactPadding
|
||||||
showDescription={false}
|
showDescription={false}
|
||||||
@ -172,7 +172,7 @@ export const UpdateEnterpriseSettings = ({
|
|||||||
clearErrors={clearSettingsErrors}
|
clearErrors={clearSettingsErrors}
|
||||||
>
|
>
|
||||||
<PermissionButton
|
<PermissionButton
|
||||||
type="submit"
|
type='submit'
|
||||||
permission={UPDATE_PROJECT}
|
permission={UPDATE_PROJECT}
|
||||||
projectId={id}
|
projectId={id}
|
||||||
data-testid={EDIT_PROJECT_SETTINGS_BTN}
|
data-testid={EDIT_PROJECT_SETTINGS_BTN}
|
||||||
|
@ -1,39 +1,39 @@
|
|||||||
import FormTemplate from "component/common/FormTemplate/FormTemplate";
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
||||||
import ProjectForm from "../../../ProjectForm/ProjectForm";
|
import ProjectForm from '../../../ProjectForm/ProjectForm';
|
||||||
import PermissionButton from "component/common/PermissionButton/PermissionButton";
|
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||||
import { UPDATE_PROJECT } from "component/providers/AccessProvider/permissions";
|
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import useProjectForm, {
|
import useProjectForm, {
|
||||||
DEFAULT_PROJECT_STICKINESS,
|
DEFAULT_PROJECT_STICKINESS,
|
||||||
} from "../../../hooks/useProjectForm";
|
} from '../../../hooks/useProjectForm';
|
||||||
import { useDefaultProjectSettings } from "hooks/useDefaultProjectSettings";
|
import { useDefaultProjectSettings } from 'hooks/useDefaultProjectSettings';
|
||||||
import { formatUnknownError } from "utils/formatUnknownError";
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import useToast from "hooks/useToast";
|
import useToast from 'hooks/useToast';
|
||||||
import { usePlausibleTracker } from "hooks/usePlausibleTracker";
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
import useProjectApi from "hooks/api/actions/useProjectApi/useProjectApi";
|
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
|
||||||
import useUiConfig from "hooks/api/getters/useUiConfig/useUiConfig";
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { IProject } from "interfaces/project";
|
import { IProject } from 'interfaces/project';
|
||||||
import useProject from "hooks/api/getters/useProject/useProject";
|
import useProject from 'hooks/api/getters/useProject/useProject';
|
||||||
import { useRequiredPathParam } from "hooks/useRequiredPathParam";
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { styled } from "@mui/material";
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
const StyledContainer = styled("div")<{ isPro: boolean }>(
|
const StyledContainer = styled('div')<{ isPro: boolean }>(
|
||||||
({ theme, isPro }) => ({
|
({ theme, isPro }) => ({
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
borderRadius: theme.spacing(2),
|
borderRadius: theme.spacing(2),
|
||||||
border: isPro ? "0" : `1px solid ${theme.palette.divider}`,
|
border: isPro ? '0' : `1px solid ${theme.palette.divider}`,
|
||||||
width: "100%",
|
width: '100%',
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
margin: "0 auto",
|
margin: '0 auto',
|
||||||
overflow: "hidden",
|
overflow: 'hidden',
|
||||||
[theme.breakpoints.down(1100)]: {
|
[theme.breakpoints.down(1100)]: {
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const StyledFormContainer = styled("div")(({ theme }) => ({
|
const StyledFormContainer = styled('div')(({ theme }) => ({
|
||||||
borderTop: `1px solid ${theme.palette.divider}`,
|
borderTop: `1px solid ${theme.palette.divider}`,
|
||||||
paddingTop: theme.spacing(4),
|
paddingTop: theme.spacing(4),
|
||||||
}));
|
}));
|
||||||
@ -41,9 +41,9 @@ const StyledFormContainer = styled("div")(({ theme }) => ({
|
|||||||
interface IUpdateProject {
|
interface IUpdateProject {
|
||||||
project: IProject;
|
project: IProject;
|
||||||
}
|
}
|
||||||
const EDIT_PROJECT_BTN = "EDIT_PROJECT_BTN";
|
const EDIT_PROJECT_BTN = 'EDIT_PROJECT_BTN';
|
||||||
export const UpdateProject = ({ project }: IUpdateProject) => {
|
export const UpdateProject = ({ project }: IUpdateProject) => {
|
||||||
const id = useRequiredPathParam("projectId");
|
const id = useRequiredPathParam('projectId');
|
||||||
const { uiConfig, isPro } = useUiConfig();
|
const { uiConfig, isPro } = useUiConfig();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const { defaultStickiness } = useDefaultProjectSettings(id);
|
const { defaultStickiness } = useDefaultProjectSettings(id);
|
||||||
@ -69,7 +69,7 @@ export const UpdateProject = ({ project }: IUpdateProject) => {
|
|||||||
project.name,
|
project.name,
|
||||||
project.description,
|
project.description,
|
||||||
defaultStickiness,
|
defaultStickiness,
|
||||||
String(project.featureLimit)
|
String(project.featureLimit),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { editProject, loading } = useProjectApi();
|
const { editProject, loading } = useProjectApi();
|
||||||
@ -94,11 +94,11 @@ export const UpdateProject = ({ project }: IUpdateProject) => {
|
|||||||
await editProject(id, payload);
|
await editProject(id, payload);
|
||||||
refetch();
|
refetch();
|
||||||
setToastData({
|
setToastData({
|
||||||
title: "Project information updated",
|
title: 'Project information updated',
|
||||||
type: "success",
|
type: 'success',
|
||||||
});
|
});
|
||||||
if (projectStickiness !== DEFAULT_PROJECT_STICKINESS) {
|
if (projectStickiness !== DEFAULT_PROJECT_STICKINESS) {
|
||||||
trackEvent("project_stickiness_set");
|
trackEvent('project_stickiness_set');
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(error));
|
setToastApiError(formatUnknownError(error));
|
||||||
@ -110,10 +110,10 @@ export const UpdateProject = ({ project }: IUpdateProject) => {
|
|||||||
<StyledContainer isPro={isPro()}>
|
<StyledContainer isPro={isPro()}>
|
||||||
<FormTemplate
|
<FormTemplate
|
||||||
loading={loading}
|
loading={loading}
|
||||||
title="General Settings"
|
title='General Settings'
|
||||||
description="Projects allows you to group feature toggles together in the management UI."
|
description='Projects allows you to group feature toggles together in the management UI.'
|
||||||
documentationLink="https://docs.getunleash.io/reference/projects"
|
documentationLink='https://docs.getunleash.io/reference/projects'
|
||||||
documentationLinkLabel="Projects documentation"
|
documentationLinkLabel='Projects documentation'
|
||||||
formatApiCode={formatProjectApiCode}
|
formatApiCode={formatProjectApiCode}
|
||||||
compactPadding
|
compactPadding
|
||||||
compact
|
compact
|
||||||
@ -132,12 +132,12 @@ export const UpdateProject = ({ project }: IUpdateProject) => {
|
|||||||
featureLimit={featureLimit}
|
featureLimit={featureLimit}
|
||||||
projectDesc={projectDesc}
|
projectDesc={projectDesc}
|
||||||
setProjectDesc={setProjectDesc}
|
setProjectDesc={setProjectDesc}
|
||||||
mode="Edit"
|
mode='Edit'
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
validateProjectId={validateProjectId}
|
validateProjectId={validateProjectId}
|
||||||
>
|
>
|
||||||
<PermissionButton
|
<PermissionButton
|
||||||
type="submit"
|
type='submit'
|
||||||
permission={UPDATE_PROJECT}
|
permission={UPDATE_PROJECT}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
data-testid={EDIT_PROJECT_BTN}
|
data-testid={EDIT_PROJECT_BTN}
|
||||||
|
Loading…
Reference in New Issue
Block a user