1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-27 00:19:39 +01:00
unleash.unleash/frontend/src/component/feature/FeatureView2/FeatureStrategies/FeatureStrategiesEnvironments/FeatureStrategiesEnvironments.tsx
2021-10-26 12:48:56 +02:00

431 lines
17 KiB
TypeScript

import { useHistory, useParams } from 'react-router-dom';
import useFeature from '../../../../../hooks/api/getters/useFeature/useFeature';
import { useStyles } from './FeatureStrategiesEnvironments.styles';
import { Tabs, Tab, useMediaQuery } from '@material-ui/core';
import TabPanel from '../../../../common/TabNav/TabPanel';
import useTabs from '../../../../../hooks/useTabs';
import FeatureStrategiesEnvironmentList from './FeatureStrategiesEnvironmentList/FeatureStrategiesEnvironmentList';
import { useContext, useEffect, useState } from 'react';
import FeatureStrategiesUIContext from '../../../../../contexts/FeatureStrategiesUIContext';
import ConditionallyRender from '../../../../common/ConditionallyRender';
import FeatureStrategiesConfigure from './FeatureStrategiesConfigure/FeatureStrategiesConfigure';
import classNames from 'classnames';
import useToast from '../../../../../hooks/useToast';
import { IFeatureViewParams } from '../../../../../interfaces/params';
import cloneDeep from 'lodash.clonedeep';
import FeatureStrategiesRefresh from './FeatureStrategiesRefresh/FeatureStrategiesRefresh';
import FeatureEnvironmentStrategyExecution from './FeatureEnvironmentStrategyExecution/FeatureEnvironmentStrategyExecution';
import { ADD_NEW_STRATEGY_ID } from '../../../../../testIds';
import NoItems from '../../../../common/NoItems/NoItems';
import ResponsiveButton from '../../../../common/ResponsiveButton/ResponsiveButton';
import { Add } from '@material-ui/icons';
import AccessContext from '../../../../../contexts/AccessContext';
import { UPDATE_FEATURE } from '../../../../providers/AccessProvider/permissions';
import useQueryParams from '../../../../../hooks/useQueryParams';
import PermissionButton from '../../../../common/PermissionButton/PermissionButton';
const FeatureStrategiesEnvironments = () => {
const smallScreen = useMediaQuery('(max-width:700px)');
const { hasAccess } = useContext(AccessContext);
const history = useHistory();
const startingTabId = 0;
const { projectId, featureId } = useParams<IFeatureViewParams>();
const { toast, setToastData } = useToast();
const [showRefreshPrompt, setShowRefreshPrompt] = useState(false);
const styles = useStyles();
const query = useQueryParams();
const addStrategy = query.get('addStrategy');
const environmentTab = query.get('environment');
const { a11yProps, activeTabIdx, setActiveTab } = useTabs(startingTabId);
const {
setActiveEnvironment,
configureNewStrategy,
expandedSidebar,
setExpandedSidebar,
featureCache,
setFeatureCache,
} = useContext(FeatureStrategiesUIContext);
const { feature } = useFeature(projectId, featureId, {
revalidateOnFocus: false,
revalidateIfStale: false,
revalidateOnReconnect: false,
refreshInterval: 5000,
});
useEffect(() => {
if (addStrategy) {
setExpandedSidebar(true);
}
if (!feature) return;
if (environmentTab) {
const env = feature.environments.find(
env => env.name === environmentTab
);
const index = feature.environments.findIndex(
env => env.name === environmentTab
);
if (index < 0 || !env) return;
setActiveEnvironment(env);
setActiveTab(index);
return;
}
if (feature?.environments?.length === 0) return;
setActiveEnvironment(feature?.environments[activeTabIdx]);
/*eslint-disable-next-line */
}, [feature]);
useEffect(() => {
if (!feature) return;
if (featureCache === null || !featureCache.createdAt) {
setFeatureCache(cloneDeep(feature));
}
/* eslint-disable-next-line */
}, [feature]);
useEffect(() => {
if (!feature) return;
if (featureCache === null) return;
if (!featureCache.createdAt) return;
const equal = compareCacheToFeature();
if (!equal) {
setShowRefreshPrompt(true);
}
/*eslint-disable-next-line */
}, [feature]);
if (!feature) return null;
const renderTabs = () => {
return featureCache?.environments?.map((env, index) => {
return (
<Tab
disabled={!!configureNewStrategy}
key={`${env.name}_${index}`}
label={env.name}
{...a11yProps(index)}
onClick={() => setActiveTab(index)}
className={styles.tabButton}
/>
);
});
};
const compareCacheToFeature = () => {
let equal = true;
// If the length of environments are different
if (!featureCache) return false;
if (
feature?.environments?.length !== featureCache?.environments?.length
) {
equal = false;
}
feature?.environments?.forEach(env => {
const cachedEnv = featureCache?.environments?.find(
cacheEnv => cacheEnv.name === env.name
);
if (!cachedEnv) {
equal = false;
return;
}
// If displayName is different
if (env?.displayName !== cachedEnv?.displayName) {
equal = false;
return;
}
// If the type of environments are different
if (env?.type !== cachedEnv?.type) {
equal = false;
return;
}
});
if (!equal) return equal;
feature?.environments?.forEach(env => {
const cachedEnv = featureCache?.environments?.find(
cachedEnv => cachedEnv.name === env.name
);
if (!cachedEnv) return;
if (cachedEnv.strategies.length !== env.strategies.length) {
equal = false;
return;
}
env?.strategies?.forEach(strategy => {
const cachedStrategy = cachedEnv?.strategies?.find(
cachedStrategy => cachedStrategy.id === strategy.id
);
// Check stickiness
if (cachedStrategy?.stickiness !== strategy?.stickiness) {
equal = false;
return;
}
if (cachedStrategy?.groupId !== strategy?.groupId) {
equal = false;
return;
}
// Check groupId
const cacheParamKeys = Object.keys(
cachedStrategy?.parameters || {}
);
const strategyParamKeys = Object.keys(
strategy?.parameters || {}
);
// Check length of parameters
if (cacheParamKeys.length !== strategyParamKeys.length) {
equal = false;
return;
}
// Make sure parameters are the same
strategyParamKeys.forEach(key => {
const found = cacheParamKeys.find(
cacheKey => cacheKey === key
);
if (!found) {
equal = false;
return;
}
});
// Check value of parameters
strategyParamKeys.forEach(key => {
const strategyValue = strategy.parameters[key];
const cachedValue = cachedStrategy.parameters[key];
if (strategyValue !== cachedValue) {
equal = false;
return;
}
});
// Check length of constraints
const cachedConstraints = cachedStrategy.constraints;
const strategyConstraints = strategy.constraints;
if (cachedConstraints.length !== strategyConstraints.length) {
equal = false;
return;
}
// Check constraints -> are we g uaranteed that constraints will occur in the same order each time?
});
});
return equal;
// If the parameter values are different
// If the constraint length is different
// If the constraint operators are different
// If the constraint values are different
// If the stickiness is different
// If the groupId is different
};
const renderTabPanels = () => {
const tabContentClasses = classNames(styles.tabContentContainer, {
[styles.containerListView]: configureNewStrategy,
});
const listContainerClasses = classNames(styles.listContainer, {
[styles.listContainerFullWidth]: expandedSidebar,
[styles.listContainerWithoutSidebar]: !hasAccess(UPDATE_FEATURE),
});
return featureCache?.environments?.map((env, index) => {
return (
<TabPanel
key={`tab_panel_${index}`}
value={activeTabIdx}
index={index}
>
<div className={tabContentClasses}>
<ConditionallyRender
condition={
env.strategies.length > 0 || expandedSidebar
}
show={
<>
<div className={listContainerClasses}>
<FeatureStrategiesEnvironmentList
strategies={env.strategies}
/>
</div>
<ConditionallyRender
condition={
!expandedSidebar &&
!configureNewStrategy &&
!smallScreen
}
show={
<FeatureEnvironmentStrategyExecution
strategies={env.strategies}
env={env}
/>
}
/>
</>
}
elseShow={
<ConditionallyRender
condition={!expandedSidebar}
show={
<NoItems>
<p
className={
styles.noItemsParagraph
}
>
No strategies added in the{' '}
{env.name} environment
</p>
<p
className={
styles.noItemsParagraph
}
>
Strategies added in this
environment will only be
executed if the SDK is using an
API key configured for this
environment.
<a
className={styles.link}
href="https://docs.getunleash.io/user_guide/environments"
>
Read more here
</a>
</p>
<ConditionallyRender
condition={hasAccess(
UPDATE_FEATURE
)}
show={
<PermissionButton
variant="contained"
permission={
UPDATE_FEATURE
}
projectId={projectId}
color="primary"
onClick={() => {
setExpandedSidebar(
prev => !prev
);
}}
>
Add your first strategy
</PermissionButton>
}
/>
</NoItems>
}
/>
}
/>
</div>
</TabPanel>
);
});
};
const handleRefresh = () => {
setFeatureCache(cloneDeep(feature));
setShowRefreshPrompt(false);
};
const handleCancel = () => {
setShowRefreshPrompt(false);
};
const classes = classNames(styles.container, {
[styles.fullWidth]: !expandedSidebar,
});
return (
<div className={classes}>
<ConditionallyRender
condition={(!expandedSidebar && smallScreen) || !smallScreen}
show={
<>
<div className={styles.environmentsHeader}>
<h2 className={styles.header}>Environments</h2>
<FeatureStrategiesRefresh
show={showRefreshPrompt}
refresh={handleRefresh}
cancel={handleCancel}
/>
<ConditionallyRender
condition={!expandedSidebar}
show={
<ResponsiveButton
data-test={ADD_NEW_STRATEGY_ID}
onClick={() =>
setExpandedSidebar(prev => !prev)
}
Icon={Add}
maxWidth="700px"
projectId={projectId}
permission={UPDATE_FEATURE}
>
Add new strategy
</ResponsiveButton>
}
/>
</div>
<div className={styles.tabContainer}>
<Tabs
value={activeTabIdx}
onChange={(_, tabId) => {
setActiveTab(tabId);
setActiveEnvironment(
feature?.environments[tabId]
);
history.replace(history.location.pathname);
}}
indicatorColor="primary"
textColor="primary"
className={styles.tabNavigation}
>
{renderTabs()}
</Tabs>
</div>
<div>
{renderTabPanels()}
<ConditionallyRender
condition={configureNewStrategy}
show={
<FeatureStrategiesConfigure
setToastData={setToastData}
/>
}
/>
</div>
{toast}
</>
}
/>
</div>
);
};
export default FeatureStrategiesEnvironments;