diff --git a/frontend/package.json b/frontend/package.json index 272c6dd788..1a050f6643 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -48,7 +48,7 @@ "@types/enzyme": "3.10.10", "@types/enzyme-adapter-react-16": "1.0.6", "@types/jest": "27.0.3", - "@types/node": "14.18.1", + "@types/node": "14.18.2", "@types/react": "17.0.37", "@types/react-dom": "17.0.11", "@types/react-router-dom": "5.3.2", @@ -87,7 +87,7 @@ "redux-devtools-extension": "2.13.9", "redux-mock-store": "1.5.4", "redux-thunk": "2.4.1", - "sass": "1.44.0", + "sass": "1.45.1", "swr": "1.0.1", "typescript": "4.5.4", "web-vitals": "2.1.2" diff --git a/frontend/src/component/common/RolloutIcon/RolloutIcon.styles.ts b/frontend/src/component/common/RolloutIcon/RolloutIcon.styles.ts index 21b4e55997..d6798c6f98 100644 --- a/frontend/src/component/common/RolloutIcon/RolloutIcon.styles.ts +++ b/frontend/src/component/common/RolloutIcon/RolloutIcon.styles.ts @@ -7,6 +7,7 @@ export const useStyles = makeStyles(theme => ({ position: 'relative', width: '50px', height: '100%', + padding: '15px 0px', }, vertical: { borderRadius: '1px', diff --git a/frontend/src/component/common/Splash/Splash.styles.ts b/frontend/src/component/common/Splash/Splash.styles.ts index 09905f1738..89d64edbe1 100644 --- a/frontend/src/component/common/Splash/Splash.styles.ts +++ b/frontend/src/component/common/Splash/Splash.styles.ts @@ -3,7 +3,6 @@ import { makeStyles } from '@material-ui/core/styles'; export const useStyles = makeStyles(theme => ({ splashMainContainer: { backgroundColor: theme.palette.primary.light, - height: '100%', width: '100%', display: 'flex', justifyContent: 'center', diff --git a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts index 4874c05e0e..1a3b2e2aa9 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.styles.ts @@ -19,6 +19,13 @@ export const useStyles = makeStyles(theme => ({ position: 'relative', paddingBottom: '1rem', }, + header: { + display: 'flex', + justifyContent: 'center', + flexDirection: 'column', + + paddingTop: '1.5rem', + }, headerTitle: { display: 'flex', alignItems: 'center', @@ -70,8 +77,8 @@ export const useStyles = makeStyles(theme => ({ alignItems: 'center', }, percentageContainer: { - width: '50px', - height: '50px', + width: '90px', + height: '90px', border: `2px solid ${theme.palette.primary.light}`, borderRadius: '50%', display: 'flex', @@ -95,6 +102,7 @@ export const useStyles = makeStyles(theme => ({ requestText: { textAlign: 'center', marginTop: '1rem', + fontSize: theme.fontSizes.smallBody, }, linkContainer: { display: 'flex', @@ -114,6 +122,29 @@ export const useStyles = makeStyles(theme => ({ fill: theme.palette.grey[400], marginBottom: '1rem', }, + strategiesText: { + fontSize: '14px', + color: theme.palette.grey[700], + }, + stratigiesInfoContainer: { + display: 'flex', + }, + noStratigiesInfoContainer: { + top: '1px', + position: 'relative', + }, + stratigiesIconsContainer: { + display: 'flex', + alignItems: 'center', + transform: 'scale(0.8)', + top: '3px', + left: '-10px', + position: 'relative', + [theme.breakpoints.down(560)]: { + marginLeft: '0px', + top: '5px', + }, + }, [theme.breakpoints.down(750)]: { accordionBodyFooter: { flexDirection: 'column', @@ -124,7 +155,7 @@ export const useStyles = makeStyles(theme => ({ }, [theme.breakpoints.down(560)]: { disabledIndicatorPos: { - top: '-8px', + top: '13px', }, headerTitle: { flexDirection: 'column', @@ -135,6 +166,9 @@ export const useStyles = makeStyles(theme => ({ truncator: { textAlign: 'center', }, + resultContainer: { + flexWrap: 'wrap', + }, }, [theme.breakpoints.down(400)]: { accordionHeader: { @@ -145,4 +179,60 @@ export const useStyles = makeStyles(theme => ({ padding: '0.5rem', }, }, + strategyIconContainer: { + marginRight: '5px', + }, + strategyIcon: { + fill: theme.palette.grey[600], + }, + container: { + display: 'flex', + alignItems: 'center', + marginLeft: '1.3rem', + [theme.breakpoints.down(560)]: { + flexDirection: 'column', + marginLeft: '0', + }, + }, + addStrategyButton: { + background: 'none', + textDecoration: 'none', + boxShadow: 'none', + color: theme.palette.primary.main, + fontWeight: 'normal', + '&:hover': { + background: 'none', + textDecoration: 'none', + boxShadow: 'none', + color: theme.palette.primary.main, + fontWeight: 'normal', + }, + '&:disabled': { + margin: '0px 16px', + height: '35px' + }, + }, + separtor: { + marginLeft: '-10px', + marginRight: '9px', + [theme.breakpoints.down(560)]: { + display: 'none', + }, + }, + resultContainer: { + display: 'flex', + width: '100%', + justifyContent: 'space-around', + }, + dataContainer: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignContent: 'center', + alignItems: 'center', + padding: '0px 15px', + }, + resultTitle: { + color: theme.palette.primary.main, + }, })); diff --git a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx index 66bd45038e..91b7624ac9 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx @@ -2,24 +2,38 @@ import { Accordion, AccordionDetails, AccordionSummary, + Tooltip, } from '@material-ui/core'; import { ExpandMore } from '@material-ui/icons'; +import React from 'react'; import { useParams } from 'react-router'; +import { useHistory } from 'react-router-dom'; import useFeature from '../../../../../../hooks/api/getters/useFeature/useFeature'; import useFeatureMetrics from '../../../../../../hooks/api/getters/useFeatureMetrics/useFeatureMetrics'; import { IFeatureEnvironment } from '../../../../../../interfaces/featureToggle'; import { IFeatureViewParams } from '../../../../../../interfaces/params'; import { getFeatureMetrics } from '../../../../../../utils/get-feature-metrics'; +import { + getFeatureStrategyIcon, + getHumanReadableStrategyName, +} from '../../../../../../utils/strategy-names'; import ConditionallyRender from '../../../../../common/ConditionallyRender'; import DisabledIndicator from '../../../../../common/DisabledIndicator/DisabledIndicator'; import EnvironmentIcon from '../../../../../common/EnvironmentIcon/EnvironmentIcon'; +import PermissionButton from '../../../../../common/PermissionButton/PermissionButton'; import StringTruncator from '../../../../../common/StringTruncator/StringTruncator'; +import { UPDATE_FEATURE } from '../../../../../providers/AccessProvider/permissions'; import { useStyles } from './FeatureOverviewEnvironment.styles'; import FeatureOverviewEnvironmentBody from './FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody'; import FeatureOverviewEnvironmentFooter from './FeatureOverviewEnvironmentFooter/FeatureOverviewEnvironmentFooter'; import FeatureOverviewEnvironmentMetrics from './FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics'; +interface IStrategyIconObject { + count: number; + Icon: React.ReactElement; + name: string; +} interface IFeatureOverviewEnvironmentProps { env: IFeatureEnvironment; } @@ -31,6 +45,7 @@ const FeatureOverviewEnvironment = ({ const { projectId, featureId } = useParams(); const { metrics } = useFeatureMetrics(projectId, featureId); const { feature } = useFeature(projectId, featureId); + const history = useHistory(); const featureMetrics = getFeatureMetrics(feature?.environments, metrics); const environmentMetric = featureMetrics.find( @@ -48,6 +63,29 @@ const FeatureOverviewEnvironment = ({ return `This environment is disabled, which means that none of your strategies are executing`; }; + const strategiesLink = `/projects/${projectId}/features2/${featureId}/strategies?environment=${featureEnvironment?.name}&addStrategy=true`; + + const getStrategyIcons = () => { + const strategyObjects = featureEnvironment?.strategies.reduce( + (acc, current) => { + if (acc[current.name]) { + acc[current.name].count = acc[current.name].count + 1; + } else { + acc[current.name] = { + count: 1, + Icon: getFeatureStrategyIcon(current.name), + }; + } + return acc; + }, + {} as { [key: string]: IStrategyIconObject } + ); + + return Object.keys(strategyObjects).map(strategyName => { + return { ...strategyObjects[strategyName], name: strategyName }; + }); + }; + return (
@@ -55,17 +93,76 @@ const FeatureOverviewEnvironment = ({ className={styles.accordionHeader} expandIcon={} > -
- - Feature toggle execution for  - +
+
+ + Feature toggle execution for  + +
+
+ history.push(strategiesLink)} + className={styles.addStrategyButton} + > + Add strategy + + | + + {getStrategyIcons()?.map( + ({ name, Icon }) => ( + +
+ +
+
+ ) + )} +
+ } + elseShow={ +
+

+ No strategies defined on this toggle +

+
+ } + /> +
+ string; @@ -39,7 +42,14 @@ const FeatureOverviewEnvironmentBody = ({ show={ <>
- Edit strategies + history.push(strategiesLink)} + maxWidth="700px" + permission={UPDATE_FEATURE} + > + Add strategy +
- - } - elseShow={ -
- -

- As long as the environment is disabled, all - requests made for this feature toggle will - return false. Add a strategy and turn on the - environment to enable it for your users. -

+
+
+

Exposure

+
+ {environmentMetric?.yes}
- } - /> - -
- Total requests {totalTraffic} -
- {calculatePercentage( - totalTraffic, - environmentMetric?.yes - )} - % +

+ Total exposure of the feature in the environment in + the last hour +

+
+
+

% exposure

+
+ {calculatePercentage( + totalTraffic, + environmentMetric?.yes + )} + % +
+

+ Total exposure of the feature in the environment in + the last hour +

+
+
+

Total requests

+
+ {environmentMetric?.yes + environmentMetric?.no} +
+

+ The total request of the feature in the environment + in the last hour +

-

- Received enabled for this feature in this environment in - the last hour. -

diff --git a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.styles.ts b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.styles.ts index fdcc581370..7d384805a7 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.styles.ts +++ b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.styles.ts @@ -17,15 +17,18 @@ export const useStyles = makeStyles(theme => ({ width: '75px', }, infoParagraph: { - maxWidth: '150px', + maxWidth: '215px', marginTop: '0.25rem', fontSize: theme.fontSizes.smallBody, }, percentage: { color: theme.palette.primary.light, - textAlign: 'center', + textAlign: 'right', fontSize: theme.fontSizes.subHeader, }, + percentageCircle: { + transform: 'scale(0.85)', + }, [theme.breakpoints.down(700)]: { infoParagraph: { display: 'none', @@ -35,8 +38,6 @@ export const useStyles = makeStyles(theme => ({ icon: { display: 'none', }, - }, - [theme.breakpoints.down(400)]: { percentageCircle: { display: 'none', }, diff --git a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.tsx b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.tsx index 446e0ea411..f0ff52afba 100644 --- a/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.tsx +++ b/frontend/src/component/feature/FeatureView2/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.tsx @@ -27,7 +27,8 @@ const FeatureOverviewEnvironmentMetrics = ({ {percentage}%

- No one has received this feature in the last hour. + The feature has been requested 0 times and + exposed 0 times in the last hour

{percentage}%

- {environmentMetric.yes} users have received the feature in - the last hour. + The feature has been requested{' '} + {environmentMetric.yes + environmentMetric.no} times{' '} + and exposed {environmentMetric.yes} times in the last + hour

- {parameters[key]}% of your user base{' '} + {parameters[key]}% of your base{' '} {constraints.length > 0 ? 'who match constraints' : ''}{' '} @@ -146,8 +146,7 @@ const FeatureStrategyExecution = ({ return (

- {strategy?.parameters[param.name]}% of your user - base{' '} + {strategy?.parameters[param.name]}% of your base{' '} {constraints?.length > 0 ? 'who match constraints' : ''}{' '} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index cd9d265900..3e6873f634 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2112,10 +2112,10 @@ resolved "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz" integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw== -"@types/node@14.18.1": - version "14.18.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.1.tgz#459886b51f52aa923dc06b9ea81cb8b1d733e9d3" - integrity sha512-fTFWOFrgAkj737w1o0HLTIgisgYHnsZfeiqhG1Ltrf/iJjudEbUwetQAsfrtVE49JGwvpEzQR+EbMkIqG4227g== +"@types/node@14.18.2": + version "14.18.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.2.tgz#00fe4d1686d5f6cf3a2f2e9a0eef42594d06abfc" + integrity sha512-fqtSN5xn/bBzDxMT77C1rJg6CsH/R49E7qsGuvdPJa20HtV5zSTuLJPNfnlyVH3wauKnkHdLggTVkOW/xP9oQg== "@types/node@^14.14.31": version "14.17.19" @@ -11265,13 +11265,14 @@ sass-loader@^10.0.5: schema-utils "^3.0.0" semver "^7.3.2" -sass@1.44.0: - version "1.44.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.44.0.tgz#619aa0a2275c097f9af5e6b8fe8a95e3056430fb" - integrity sha512-0hLREbHFXGQqls/K8X+koeP+ogFRPF4ZqetVB19b7Cst9Er8cOR0rc6RU7MaI4W1JmUShd1BPgPoeqmmgMMYFw== +sass@1.45.1: + version "1.45.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.45.1.tgz#fa03951f924d1ba5762949567eaf660e608a1ab0" + integrity sha512-pwPRiq29UR0o4X3fiQyCtrESldXvUQAAE0QmcJTpsI4kuHHcLzZ54M1oNBVIXybQv8QF2zfkpFcTxp8ta97dUA== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" sax@~1.2.4: version "1.2.4" @@ -11607,6 +11608,11 @@ source-list-map@^2.0.0: resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== +"source-map-js@>=0.6.2 <2.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" + integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== + source-map-js@^0.6.2: version "0.6.2" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz"