2022-03-09 14:59:24 +01:00
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
import { FeatureStrategyForm } from 'component/feature/FeatureStrategy/FeatureStrategyForm/FeatureStrategyForm';
|
2022-03-28 10:49:59 +02:00
|
|
|
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
|
|
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
2022-03-09 14:59:24 +01:00
|
|
|
import { useRequiredQueryParam } from 'hooks/useRequiredQueryParam';
|
|
|
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
2022-03-28 10:49:59 +02:00
|
|
|
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
|
2022-03-25 12:34:20 +01:00
|
|
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
2022-03-09 14:59:24 +01:00
|
|
|
import { useHistory } from 'react-router-dom';
|
2022-03-28 10:49:59 +02:00
|
|
|
import useToast from 'hooks/useToast';
|
2022-05-04 15:16:34 +02:00
|
|
|
import { IFeatureStrategy, IFeatureStrategyPayload } from 'interfaces/strategy';
|
2022-03-09 14:59:24 +01:00
|
|
|
import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
2022-03-29 09:30:57 +02:00
|
|
|
import { ISegment } from 'interfaces/segment';
|
|
|
|
import { useSegmentsApi } from 'hooks/api/actions/useSegmentsApi/useSegmentsApi';
|
|
|
|
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
|
|
|
|
import { formatStrategyName } from 'utils/strategyNames';
|
2022-04-26 09:54:16 +02:00
|
|
|
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
|
2022-03-09 14:59:24 +01:00
|
|
|
|
|
|
|
export const FeatureStrategyEdit = () => {
|
|
|
|
const projectId = useRequiredPathParam('projectId');
|
|
|
|
const featureId = useRequiredPathParam('featureId');
|
|
|
|
const environmentId = useRequiredQueryParam('environmentId');
|
|
|
|
const strategyId = useRequiredQueryParam('strategyId');
|
|
|
|
|
|
|
|
const [strategy, setStrategy] = useState<Partial<IFeatureStrategy>>({});
|
2022-03-29 09:30:57 +02:00
|
|
|
const [segments, setSegments] = useState<ISegment[]>([]);
|
2022-03-09 14:59:24 +01:00
|
|
|
const { updateStrategyOnFeature, loading } = useFeatureStrategyApi();
|
2022-03-29 09:30:57 +02:00
|
|
|
const { setStrategySegments } = useSegmentsApi();
|
2022-03-09 14:59:24 +01:00
|
|
|
const { setToastData, setToastApiError } = useToast();
|
|
|
|
const { uiConfig } = useUiConfig();
|
|
|
|
const { unleashUrl } = uiConfig;
|
|
|
|
const { push } = useHistory();
|
|
|
|
|
2022-04-26 09:54:16 +02:00
|
|
|
const { feature, refetchFeature } = useFeatureImmutable(
|
|
|
|
projectId,
|
|
|
|
featureId
|
|
|
|
);
|
|
|
|
|
2022-04-01 11:11:53 +02:00
|
|
|
const {
|
|
|
|
segments: savedStrategySegments,
|
|
|
|
refetchSegments: refetchSavedStrategySegments,
|
|
|
|
} = useSegments(strategyId);
|
|
|
|
|
2022-03-09 14:59:24 +01:00
|
|
|
useEffect(() => {
|
|
|
|
const savedStrategy = feature.environments
|
|
|
|
.flatMap(environment => environment.strategies)
|
|
|
|
.find(strategy => strategy.id === strategyId);
|
|
|
|
setStrategy(prev => ({ ...prev, ...savedStrategy }));
|
|
|
|
}, [strategyId, feature]);
|
|
|
|
|
2022-03-29 09:30:57 +02:00
|
|
|
useEffect(() => {
|
|
|
|
// Fill in the selected segments once they've been fetched.
|
|
|
|
savedStrategySegments && setSegments(savedStrategySegments);
|
|
|
|
}, [savedStrategySegments]);
|
|
|
|
|
2022-03-09 14:59:24 +01:00
|
|
|
const onSubmit = async () => {
|
|
|
|
try {
|
|
|
|
await updateStrategyOnFeature(
|
|
|
|
projectId,
|
|
|
|
featureId,
|
|
|
|
environmentId,
|
|
|
|
strategyId,
|
|
|
|
createStrategyPayload(strategy)
|
|
|
|
);
|
2022-04-01 11:11:53 +02:00
|
|
|
if (uiConfig.flags.SE) {
|
|
|
|
await setStrategySegments({
|
|
|
|
environmentId,
|
|
|
|
projectId,
|
|
|
|
strategyId,
|
|
|
|
segmentIds: segments.map(s => s.id),
|
|
|
|
});
|
|
|
|
await refetchSavedStrategySegments();
|
|
|
|
}
|
2022-03-09 14:59:24 +01:00
|
|
|
setToastData({
|
|
|
|
title: 'Strategy updated',
|
|
|
|
type: 'success',
|
|
|
|
confetti: true,
|
|
|
|
});
|
|
|
|
refetchFeature();
|
|
|
|
push(formatFeaturePath(projectId, featureId));
|
|
|
|
} catch (error: unknown) {
|
|
|
|
setToastApiError(formatUnknownError(error));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Wait until the strategy has loaded before showing the form.
|
|
|
|
if (!strategy.id) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<FormTemplate
|
|
|
|
modal
|
2022-03-29 09:30:57 +02:00
|
|
|
title={formatStrategyName(strategy.name ?? '')}
|
2022-03-09 14:59:24 +01:00
|
|
|
description={featureStrategyHelp}
|
|
|
|
documentationLink={featureStrategyDocsLink}
|
2022-04-25 09:36:23 +02:00
|
|
|
documentationLinkLabel={featureStrategyDocsLinkLabel}
|
2022-03-09 14:59:24 +01:00
|
|
|
formatApiCode={() =>
|
|
|
|
formatUpdateStrategyApiCode(
|
|
|
|
projectId,
|
|
|
|
featureId,
|
|
|
|
environmentId,
|
|
|
|
strategy,
|
|
|
|
unleashUrl
|
|
|
|
)
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<FeatureStrategyForm
|
|
|
|
feature={feature}
|
|
|
|
strategy={strategy}
|
|
|
|
setStrategy={setStrategy}
|
2022-03-29 09:30:57 +02:00
|
|
|
segments={segments}
|
|
|
|
setSegments={setSegments}
|
2022-03-09 14:59:24 +01:00
|
|
|
environmentId={environmentId}
|
|
|
|
onSubmit={onSubmit}
|
|
|
|
loading={loading}
|
|
|
|
permission={UPDATE_FEATURE_STRATEGY}
|
|
|
|
/>
|
|
|
|
</FormTemplate>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const createStrategyPayload = (
|
|
|
|
strategy: Partial<IFeatureStrategy>
|
2022-05-04 15:16:34 +02:00
|
|
|
): IFeatureStrategyPayload => {
|
2022-03-09 14:59:24 +01:00
|
|
|
return {
|
|
|
|
name: strategy.name,
|
|
|
|
constraints: strategy.constraints ?? [],
|
|
|
|
parameters: strategy.parameters ?? {},
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
export const formatFeaturePath = (
|
|
|
|
projectId: string,
|
|
|
|
featureId: string
|
|
|
|
): string => {
|
|
|
|
return `/projects/${projectId}/features/${featureId}`;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const formatEditStrategyPath = (
|
|
|
|
projectId: string,
|
|
|
|
featureId: string,
|
|
|
|
environmentId: string,
|
|
|
|
strategyId: string
|
|
|
|
): string => {
|
|
|
|
const params = new URLSearchParams({ environmentId, strategyId });
|
|
|
|
|
|
|
|
return `/projects/${projectId}/features/${featureId}/strategies/edit?${params}`;
|
|
|
|
};
|
|
|
|
|
|
|
|
const formatUpdateStrategyApiCode = (
|
|
|
|
projectId: string,
|
|
|
|
featureId: string,
|
|
|
|
environmentId: string,
|
|
|
|
strategy: Partial<IFeatureStrategy>,
|
|
|
|
unleashUrl?: string
|
|
|
|
): string => {
|
|
|
|
if (!unleashUrl) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
const url = `${unleashUrl}/api/admin/projects/${projectId}/features/${featureId}/${environmentId}/development/strategies/${strategy.id}`;
|
|
|
|
const payload = JSON.stringify(strategy, undefined, 2);
|
|
|
|
|
|
|
|
return `curl --location --request PUT '${url}' \\
|
|
|
|
--header 'Authorization: INSERT_API_KEY' \\
|
|
|
|
--header 'Content-Type: application/json' \\
|
|
|
|
--data-raw '${payload}'`;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const featureStrategyHelp = `
|
|
|
|
An activation strategy will only run when a feature toggle is enabled and provides a way to control who will get access to the feature.
|
|
|
|
If any of a feature toggle's activation strategies returns true, the user will get access.
|
|
|
|
`;
|
|
|
|
|
|
|
|
export const featureStrategyDocsLink =
|
|
|
|
'https://docs.getunleash.io/user_guide/activation_strategy';
|
2022-04-25 09:36:23 +02:00
|
|
|
|
|
|
|
export const featureStrategyDocsLinkLabel = 'Strategies documentation';
|