diff --git a/frontend/.github/workflows/release_to_cdn.yml b/frontend/.github/workflows/release_to_cdn.yml
new file mode 100644
index 0000000000..018b0735df
--- /dev/null
+++ b/frontend/.github/workflows/release_to_cdn.yml
@@ -0,0 +1,34 @@
+name: 'Release static assets to CDN'
+on:
+ push:
+ tags:
+ - 'v*'
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [14.x]
+
+ steps:
+ - uses: actions/checkout@v1
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node-version }}
+ - run: yarn
+ - uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
+ - name: Get the version
+ id: get_version
+ run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
+ - name: Deploy static assets to S3
+ run: |
+ aws s3 cp build/ s3://getunleash-static/unleash/${{ steps.get_version.outputs.VERSION }} --recursive
+
diff --git a/frontend/package.json b/frontend/package.json
index 1a050f6643..c7ba64a9b5 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "unleash-frontend",
"description": "unleash your features",
- "version": "4.4.0",
+ "version": "4.4.1",
"keywords": [
"unleash",
"feature toggle",
@@ -45,11 +45,11 @@
"@testing-library/user-event": "13.5.0",
"@types/debounce": "1.2.1",
"@types/deep-diff": "1.0.1",
- "@types/enzyme": "3.10.10",
+ "@types/enzyme": "3.10.11",
"@types/enzyme-adapter-react-16": "1.0.6",
- "@types/jest": "27.0.3",
- "@types/node": "14.18.2",
- "@types/react": "17.0.37",
+ "@types/jest": "27.4.0",
+ "@types/node": "14.18.5",
+ "@types/react": "17.0.38",
"@types/react-dom": "17.0.11",
"@types/react-router-dom": "5.3.2",
"@types/react-timeago": "4.1.3",
@@ -60,7 +60,7 @@
"craco": "0.0.3",
"css-loader": "6.5.1",
"cypress": "8.7.0",
- "date-fns": "2.27.0",
+ "date-fns": "2.28.0",
"debounce": "1.2.1",
"deep-diff": "1.0.2",
"enzyme": "3.11.0",
@@ -75,8 +75,8 @@
"node-fetch": "2.6.6",
"prettier": "2.5.1",
"react": "17.0.2",
- "react-dnd": "14.0.4",
- "react-dnd-html5-backend": "14.0.2",
+ "react-dnd": "14.0.5",
+ "react-dnd-html5-backend": "14.1.0",
"react-dom": "17.0.2",
"react-outside-click-handler": "1.3.0",
"react-redux": "7.2.6",
@@ -87,7 +87,7 @@
"redux-devtools-extension": "2.13.9",
"redux-mock-store": "1.5.4",
"redux-thunk": "2.4.1",
- "sass": "1.45.1",
+ "sass": "1.46.0",
"swr": "1.0.1",
"typescript": "4.5.4",
"web-vitals": "2.1.2"
diff --git a/frontend/public/index.html b/frontend/public/index.html
index 3006ccce7d..3206f3640c 100644
--- a/frontend/public/index.html
+++ b/frontend/public/index.html
@@ -5,6 +5,7 @@
+
Unleash - Enterprise ready feature toggles
diff --git a/frontend/src/component/admin/users/change-password-component.jsx b/frontend/src/component/admin/users/change-password-component.jsx
index cb040e043b..baaf88d426 100644
--- a/frontend/src/component/admin/users/change-password-component.jsx
+++ b/frontend/src/component/admin/users/change-password-component.jsx
@@ -1,7 +1,7 @@
import { useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
-import { TextField, Typography, Avatar } from '@material-ui/core';
+import { Typography, Avatar } from '@material-ui/core';
import { trim } from '../../common/util';
import { modalStyles } from './util';
import Dialogue from '../../common/Dialogue/Dialogue';
@@ -10,6 +10,7 @@ import { useCommonStyles } from '../../../common.styles';
import PasswordMatcher from '../../user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
import ConditionallyRender from '../../common/ConditionallyRender';
import { Alert } from '@material-ui/lab';
+import PasswordField from '../../common/PasswordField/PasswordField';
function ChangePassword({
showDialog,
@@ -109,26 +110,20 @@ function ChangePassword({
/>
{error.general}
-
-
{
+ const [showPassword, setShowPassword] = useState(false);
+
+ const handleClickShowPassword = () => {
+ setShowPassword(!showPassword);
+ };
+
+ const handleMouseDownPassword = (
+ e: React.MouseEvent
+ ) => {
+ e.preventDefault();
+ };
+
+ return (
+
+
+ {showPassword ? : }
+
+
+ ),
+ }}
+ {...rest}
+ />
+ );
+};
+
+export default PasswordField;
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/context/form-context-component.jsx b/frontend/src/component/context/form-context-component.jsx
index e5a1d2df48..3616acf8d6 100644
--- a/frontend/src/component/context/form-context-component.jsx
+++ b/frontend/src/component/context/form-context-component.jsx
@@ -269,7 +269,7 @@ class AddContextComponent extends Component {
specific values of this context field. PS! Not all
client SDK's support this feature yet!{' '}
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 835ff44f51..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',
@@ -115,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',
@@ -125,7 +155,7 @@ export const useStyles = makeStyles(theme => ({
},
[theme.breakpoints.down(560)]: {
disabledIndicatorPos: {
- top: '-8px',
+ top: '13px',
},
headerTitle: {
flexDirection: 'column',
@@ -149,6 +179,46 @@ 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%',
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
+
{
const commonStyles = useCommonStyles();
@@ -109,16 +110,13 @@ const HostedAuth = ({ authDetails, passwordLogin }) => {
variant="outlined"
size="small"
/>
- setPassword(evt.target.value)}
name="password"
- type="password"
value={password}
error={!!passwordError}
helperText={passwordError}
- variant="outlined"
- size="small"
/>