1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-04 00:18:01 +01:00

Merge branch 'main' into archive_table

This commit is contained in:
Fredrik Strand Oseberg 2022-06-10 16:22:07 +02:00 committed by GitHub
commit bd4651b9ca
13 changed files with 77 additions and 17 deletions

View File

@ -13,15 +13,21 @@ import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
import { SplashPageRedirect } from 'component/splash/SplashPageRedirect/SplashPageRedirect';
import { useStyles } from './App.styles';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
export const App = () => {
const { classes: styles } = useStyles();
const { authDetails } = useAuthDetails();
const { user } = useAuthUser();
const { isOss } = useUiConfig();
const isLoggedIn = Boolean(user?.id);
const hasFetchedAuth = Boolean(authDetails || user);
usePlausibleTracker();
const availableRoutes = isOss()
? routes.filter(route => !route.enterprise)
: routes;
return (
<SWRProvider isUnauthorized={!isLoggedIn}>
<ConditionallyRender
@ -32,7 +38,7 @@ export const App = () => {
<ToastRenderer />
<LayoutPicker>
<Routes>
{routes.map(route => (
{availableRoutes.map(route => (
<Route
key={route.path}
path={route.path}

View File

@ -20,6 +20,7 @@ interface IPermissionIconButtonProps {
type?: 'button';
edge?: IconButtonProps['edge'];
tooltipProps?: Omit<ITooltipResolverProps, 'children'>;
sx?: IconButtonProps['sx'];
size?: string;
}

View File

@ -11,6 +11,7 @@ exports[`returns all baseRoutes 1`] = `
},
{
"component": [Function],
"enterprise": true,
"menu": {},
"parent": "/projects",
"path": "/projects/create",
@ -19,6 +20,7 @@ exports[`returns all baseRoutes 1`] = `
},
{
"component": [Function],
"enterprise": true,
"menu": {},
"parent": "/projects",
"path": "/projects/:projectId/edit",

View File

@ -70,6 +70,7 @@ export const routes: IRoute[] = [
title: 'Create',
component: CreateProject,
type: 'protected',
enterprise: true,
menu: {},
},
{
@ -78,6 +79,7 @@ export const routes: IRoute[] = [
title: ':projectId',
component: EditProject,
type: 'protected',
enterprise: true,
menu: {},
},
{

View File

@ -10,13 +10,18 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useContext } from 'react';
import AccessContext from 'contexts/AccessContext';
import { Alert } from '@mui/material';
const EditProject = () => {
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
const { hasAccess } = useContext(AccessContext);
const id = useRequiredPathParam('projectId');
const { project } = useProject(id);
const navigate = useNavigate();
const {
projectId,
projectName,
@ -68,6 +73,12 @@ const EditProject = () => {
navigate(-1);
};
const accessDeniedAlert = !hasAccess(UPDATE_PROJECT, projectId) && (
<Alert severity="error" sx={{ mb: 4 }}>
You do not have the required permissions to edit this project.
</Alert>
);
return (
<FormTemplate
loading={loading}
@ -77,6 +88,7 @@ const EditProject = () => {
documentationLinkLabel="Projects documentation"
formatApiCode={formatApiCode}
>
{accessDeniedAlert}
<ProjectForm
errors={errors}
handleSubmit={handleSubmit}
@ -91,7 +103,10 @@ const EditProject = () => {
clearErrors={clearErrors}
validateProjectId={validateProjectId}
>
<UpdateButton permission={UPDATE_PROJECT} />
<UpdateButton
permission={UPDATE_PROJECT}
projectId={projectId}
/>
</ProjectForm>
</FormTemplate>
);

View File

@ -19,6 +19,7 @@ import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import { TabPanel } from 'component/common/TabNav/TabPanel/TabPanel';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
const Project = () => {
const projectId = useRequiredPathParam('projectId');
@ -29,6 +30,7 @@ const Project = () => {
const { setToastData } = useToast();
const { classes: styles } = useStyles();
const navigate = useNavigate();
const { isOss } = useUiConfig();
const basePath = `/projects/${projectId}`;
const tabData = [
@ -118,7 +120,8 @@ const Project = () => {
</div>
<PermissionIconButton
permission={UPDATE_PROJECT}
projectId={project?.id}
projectId={projectId}
sx={{ visibility: isOss() ? 'hidden' : 'visible' }}
onClick={() =>
navigate(`/projects/${projectId}/edit`)
}

View File

@ -33,7 +33,7 @@ const ProjectInfo = ({
}: IProjectInfoProps) => {
const { classes: themeStyles } = useThemeStyles();
const { classes: styles } = useStyles();
const { uiConfig } = useUiConfig();
const { uiConfig, isOss } = useUiConfig();
let link = `/admin/users`;
@ -52,6 +52,7 @@ const ProjectInfo = ({
<div>
<PermissionIconButton
permission={UPDATE_PROJECT}
hidden={isOss()}
projectId={id}
component={Link}
className={permissionButtonClass}

View File

@ -2,7 +2,7 @@ import { Card, Menu, MenuItem } from '@mui/material';
import { useStyles } from './ProjectCard.styles';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import { ReactComponent as ProjectIcon } from 'assets/icons/projectIcon.svg';
import { useState, SyntheticEvent } from 'react';
import React, { useState, SyntheticEvent, useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
@ -11,8 +11,14 @@ import { Delete, Edit } from '@mui/icons-material';
import { getProjectEditPath } from 'utils/routePathHelpers';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import useToast from 'hooks/useToast';
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import {
UPDATE_PROJECT,
DELETE_PROJECT,
} from 'component/providers/AccessProvider/permissions';
import { formatUnknownError } from 'utils/formatUnknownError';
import AccessContext from 'contexts/AccessContext';
import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
interface IProjectCardProps {
name: string;
@ -32,6 +38,8 @@ export const ProjectCard = ({
id,
}: IProjectCardProps) => {
const { classes } = useStyles();
const { hasAccess } = useContext(AccessContext);
const { isOss } = useUiConfig();
const { refetch: refetchProjectOverview } = useProjects();
const [anchorEl, setAnchorEl] = useState(null);
const [showDelDialog, setShowDelDialog] = useState(false);
@ -45,7 +53,7 @@ export const ProjectCard = ({
setAnchorEl(e.currentTarget);
};
const onRemoveProject = async (e: Event) => {
const onRemoveProject = async (e: React.SyntheticEvent) => {
e.preventDefault();
try {
await deleteProject(id);
@ -62,6 +70,9 @@ export const ProjectCard = ({
setAnchorEl(null);
};
const canDeleteProject =
hasAccess(DELETE_PROJECT, id) && id !== DEFAULT_PROJECT_ID;
return (
<Card className={classes.projectCard} onMouseEnter={onHover}>
<div className={classes.header} data-loading>
@ -69,6 +80,7 @@ export const ProjectCard = ({
<PermissionIconButton
permission={UPDATE_PROJECT}
hidden={isOss()}
projectId={id}
data-loading
onClick={handleClick}
@ -85,6 +97,9 @@ export const ProjectCard = ({
open={Boolean(anchorEl)}
anchorEl={anchorEl}
style={{ top: 0, left: -100 }}
onClick={event => {
event.preventDefault();
}}
onClose={(event: SyntheticEvent) => {
event.preventDefault();
setAnchorEl(null);
@ -104,9 +119,12 @@ export const ProjectCard = ({
e.preventDefault();
setShowDelDialog(true);
}}
disabled={!canDeleteProject}
>
<Delete className={classes.icon} />
Delete project
{id === DEFAULT_PROJECT_ID && !canDeleteProject
? "You can't delete the default project"
: 'Delete project'}
</MenuItem>
</Menu>
</div>
@ -136,9 +154,9 @@ export const ProjectCard = ({
</div>
<Dialogue
open={showDelDialog}
// @ts-expect-error
onClick={onRemoveProject}
onClose={() => {
onClose={event => {
event.preventDefault();
setAnchorEl(null);
setShowDelDialog(false);
}}

View File

@ -118,12 +118,12 @@ export const ProjectListNew = () => {
className={styles.cardLink}
>
<ProjectCard
onHover={() => handleHover(project?.id)}
name={project?.name}
memberCount={project?.memberCount ?? 0}
health={project?.health}
id={project?.id}
featureCount={project?.featureCount}
onHover={() => handleHover(project.id)}
name={project.name}
memberCount={project.memberCount ?? 0}
health={project.health}
id={project.id}
featureCount={project.featureCount}
/>
</Link>
);

View File

@ -11,6 +11,7 @@ export const UPDATE_CONTEXT_FIELD = 'UPDATE_CONTEXT_FIELD';
export const DELETE_CONTEXT_FIELD = 'DELETE_CONTEXT_FIELD';
export const CREATE_PROJECT = 'CREATE_PROJECT';
export const UPDATE_PROJECT = 'UPDATE_PROJECT';
export const DELETE_PROJECT = 'DELETE_PROJECT';
export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE';
export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE';
export const CREATE_ADDON = 'CREATE_ADDON';

View File

@ -1,7 +1,8 @@
import useProjects from 'hooks/api/getters/useProjects/useProjects';
const DEFAULT_PROJECT_ID = 'default';
export const DEFAULT_PROJECT_ID = 'default';
// Get the default project ID, or the first ID if there is no default project.
export const useDefaultProjectId = (): string | undefined => {
const { projects = [] } = useProjects();

View File

@ -8,6 +8,7 @@ export interface IRoute {
parent?: string;
flag?: string;
hidden?: boolean;
enterprise?: boolean;
component: VoidFunctionComponent;
menu: IRouteMenu;
}

View File

@ -265,5 +265,14 @@ export default createTheme({
},
},
},
MuiMenuItem: {
styleOverrides: {
root: {
'&.Mui-disabled': {
opacity: 0.66,
},
},
},
},
},
});