Skip to content

Commit

Permalink
Strategy title frontend (#3556)
Browse files Browse the repository at this point in the history
Ability to add a title to a strategy
  • Loading branch information
Tymek committed Apr 19, 2023
1 parent cd73442 commit 169b2bd
Show file tree
Hide file tree
Showing 30 changed files with 232 additions and 46 deletions.
Expand Up @@ -9,6 +9,7 @@ import {
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PlaygroundStrategySchema } from 'openapi';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';

interface IStrategyItemContainerProps {
strategy: IFeatureStrategy | PlaygroundStrategySchema;
Expand Down Expand Up @@ -69,6 +70,7 @@ export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
style = {},
}) => {
const Icon = getFeatureStrategyIcon(strategy.name);
const { uiConfig } = useUiConfig();

return (
<Box sx={{ position: 'relative' }}>
Expand Down Expand Up @@ -105,7 +107,11 @@ export const StrategyItemContainer: FC<IStrategyItemContainerProps> = ({
<StringTruncator
maxWidth="150"
maxLength={15}
text={formatStrategyName(strategy.name)}
text={formatStrategyName(
uiConfig?.flags?.strategyTitle
? strategy.title || strategy.name
: strategy.name
)}
/>
<Box
sx={{
Expand Down
Expand Up @@ -29,6 +29,7 @@ import { comparisonModerator } from '../featureStrategy.utils';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';

export const FeatureStrategyCreate = () => {
const projectId = useRequiredPathParam('projectId');
Expand All @@ -52,6 +53,7 @@ export const FeatureStrategyCreate = () => {
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const { refetch: refetchChangeRequests } =
usePendingChangeRequests(projectId);
const { trackEvent } = usePlausibleTracker();

const { data, staleDataNotification, forceRefreshCache } =
useCollaborateData<IFeatureToggle>(
Expand Down Expand Up @@ -115,6 +117,13 @@ export const FeatureStrategyCreate = () => {
const onSubmit = async () => {
const payload = createStrategyPayload(strategy, segments);

trackEvent('strategyTitle', {
props: {
hasTitle: Boolean(strategy.title),
on: 'create',
},
});

try {
if (isChangeRequestConfigured(environmentId)) {
await onStrategyRequestAdd(payload);
Expand Down
Expand Up @@ -27,6 +27,7 @@ import { comparisonModerator } from '../featureStrategy.utils';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';

export const FeatureStrategyEdit = () => {
const projectId = useRequiredPathParam('projectId');
Expand All @@ -47,6 +48,7 @@ export const FeatureStrategyEdit = () => {
const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId);
const { refetch: refetchChangeRequests } =
usePendingChangeRequests(projectId);
const { trackEvent } = usePlausibleTracker();

const { feature, refetchFeature } = useFeature(projectId, featureId);

Expand Down Expand Up @@ -101,6 +103,13 @@ export const FeatureStrategyEdit = () => {
payload
);

trackEvent('strategyTitle', {
props: {
hasTitle: Boolean(strategy.title),
on: 'edit',
},
});

await refetchSavedStrategySegments();
setToastData({
title: 'Strategy updated',
Expand Down Expand Up @@ -189,6 +198,7 @@ export const createStrategyPayload = (
segments: ISegment[]
): IFeatureStrategyPayload => ({
name: strategy.name,
title: strategy.title,
constraints: strategy.constraints ?? [],
parameters: strategy.parameters ?? {},
segments: segments.map(segment => segment.id),
Expand Down
Expand Up @@ -29,6 +29,7 @@ import { formatFeaturePath } from '../FeatureStrategyEdit/FeatureStrategyEdit';
import { useChangeRequestInReviewWarning } from 'hooks/useChangeRequestInReviewWarning';
import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests';
import { useHasProjectEnvironmentAccess } from 'hooks/useHasAccess';
import { FeatureStrategyTitle } from './FeatureStrategyTitle/FeatureStrategyTitle';

interface IFeatureStrategyFormProps {
feature: IFeatureToggle;
Expand Down Expand Up @@ -217,6 +218,20 @@ export const FeatureStrategyForm = ({
/>
}
/>
<ConditionallyRender
condition={Boolean(uiConfig?.flags?.strategyTitle)}
show={
<FeatureStrategyTitle
title={strategy.title || ''}
setTitle={title => {
setStrategy(prev => ({
...prev,
title,
}));
}}
/>
}
/>
<FeatureStrategyConstraints
projectId={feature.project}
environmentId={environmentId}
Expand Down
@@ -0,0 +1,39 @@
import { Box, Typography } from '@mui/material';
import Input from 'component/common/Input/Input';
import { VFC } from 'react';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';

interface IFeatureStrategyTitleProps {
title: string;
setTitle: (title: string) => void;
}

export const FeatureStrategyTitle: VFC<IFeatureStrategyTitleProps> = ({
title,
setTitle,
}) => {
const { uiConfig } = useUiConfig();

if (!uiConfig.flags.strategyTitle) {
return null;
}

return (
<Box sx={{ paddingBottom: theme => theme.spacing(2) }}>
<Typography
sx={{
paddingBottom: theme => theme.spacing(2),
}}
>
What would you like to call this strategy? (optional)
</Typography>
<Input
label="Strategy title"
id="title-input"
value={title}
onChange={e => setTitle(e.target.value)}
sx={{ width: '100%' }}
/>
</Box>
);
};
3 changes: 2 additions & 1 deletion frontend/src/hooks/usePlausibleTracker.ts
Expand Up @@ -23,7 +23,8 @@ export type CustomEvents =
| 'project_api_tokens'
| 'project_stickiness_set'
| 'notifications'
| 'batch_operations';
| 'batch_operations'
| 'strategyTitle';

export const usePlausibleTracker = () => {
const plausible = useContext(PlausibleContext);
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/interfaces/strategy.ts
Expand Up @@ -4,6 +4,7 @@ export interface IFeatureStrategy {
id: string;
strategyName?: string;
name: string;
title?: string;
constraints: IConstraint[];
parameters: IFeatureStrategyParameters;
featureName?: string;
Expand All @@ -19,6 +20,7 @@ export interface IFeatureStrategyParameters {
export interface IFeatureStrategyPayload {
id?: string;
name?: string;
title?: string;
constraints: IConstraint[];
parameters: IFeatureStrategyParameters;
segments?: number[];
Expand Down
1 change: 1 addition & 0 deletions frontend/src/interfaces/uiConfig.ts
Expand Up @@ -52,6 +52,7 @@ export interface IFlags {
projectScopedStickiness?: boolean;
personalAccessTokensKillSwitch?: boolean;
demo?: boolean;
strategyTitle?: boolean;
}

export interface IVersionInfo {
Expand Down
23 changes: 1 addition & 22 deletions frontend/src/openapi/models/_exportParams.ts
Expand Up @@ -6,32 +6,11 @@
import type { _ExportFormat } from './_exportFormat';

export type _ExportParams = {
/**
* Desired export format. Must be either `json` or `yaml`.
*/
format?: _ExportFormat;
/**
* Whether exported data should be downloaded as a file.
*/
download?: string;
/**
* Whether strategies should be included in the exported data.
*/
download?: boolean | string | number;
strategies?: boolean | string | number;
/**
* Whether feature toggles should be included in the exported data.
*/
featureToggles?: boolean | string | number;
/**
* Whether projects should be included in the exported data.
*/
projects?: boolean | string | number;
/**
* Whether tag types, tags, and feature_tags should be included in the exported data.
*/
tags?: boolean | string | number;
/**
* Whether environments should be included in the exported data.
*/
environments?: boolean | string | number;
};
37 changes: 37 additions & 0 deletions frontend/src/openapi/models/addonCreateUpdateSchema.ts
@@ -0,0 +1,37 @@
/**
* Generated by Orval
* Do not edit manually.
* See `gen:api` script in package.json
*/
import type { AddonCreateUpdateSchemaParameters } from './addonCreateUpdateSchemaParameters';

/**
* Data required to create or update an [Unleash addon](https://docs.getunleash.io/reference/addons) instance.
*/
export interface AddonCreateUpdateSchema {
/** The addon provider, such as "webhook" or "slack". This string is **case sensitive** and maps to the provider's `name` property.
The list of all supported providers and their parameters for a specific Unleash instance can be found by making a GET request to the `api/admin/addons` endpoint: the `providers` property of that response will contain all available providers.
The default set of providers can be found in the [addons reference documentation](https://docs.getunleash.io/reference/addons). The default supported options are:
- `datadog` for [Datadog](https://docs.getunleash.io/reference/addons/datadog)
- `slack` for [Slack](https://docs.getunleash.io/reference/addons/slack)
- `teams` for [Microsoft Teams](https://docs.getunleash.io/reference/addons/teams)
- `webhook` for [webhooks](https://docs.getunleash.io/reference/addons/webhook)
The provider you choose for your addon dictates what properties the `parameters` object needs. Refer to the documentation for each provider for more information.
*/
provider: string;
/** A description of the addon. */
description?: string;
/** Whether the addon should be enabled or not. */
enabled: boolean;
/** Parameters for the addon provider. This object has different required and optional properties depending on the provider you choose. Consult the documentation for details. */
parameters: AddonCreateUpdateSchemaParameters;
/** The event types that will trigger this specific addon. */
events: string[];
/** The projects that this addon will listen to events from. An empty list means it will listen to events from **all** projects. */
projects?: string[];
/** The list of environments that this addon will listen to events from. An empty list means it will listen to events from **all** environments. */
environments?: string[];
}
10 changes: 10 additions & 0 deletions frontend/src/openapi/models/addonCreateUpdateSchemaParameters.ts
@@ -0,0 +1,10 @@
/**
* Generated by Orval
* Do not edit manually.
* See `gen:api` script in package.json
*/

/**
* Parameters for the addon provider. This object has different required and optional properties depending on the provider you choose. Consult the documentation for details.
*/
export type AddonCreateUpdateSchemaParameters = { [key: string]: unknown };
10 changes: 10 additions & 0 deletions frontend/src/openapi/models/addonParameterSchema.ts
Expand Up @@ -4,12 +4,22 @@
* See `gen:api` script in package.json
*/

/**
* An addon parameter definition.
*/
export interface AddonParameterSchema {
/** The name of the parameter as it is used in code. References to this parameter should use this value. */
name: string;
/** The name of the parameter as it is shown to the end user in the Admin UI. */
displayName: string;
/** The type of the parameter. Corresponds roughly to [HTML `input` field types](https://developer.mozilla.org/docs/Web/HTML/Element/Input#input_types). Multi-line inut fields are indicated as `textfield` (equivalent to the HTML `textarea` tag). */
type: string;
/** A description of the parameter. This should explain to the end user what the parameter is used for. */
description?: string;
/** The default value for this parameter. This value is used if no other value is provided. */
placeholder?: string;
/** Whether this parameter is required or not. If a parameter is required, you must give it a value when you create the addon. If it is not required it can be left out. It may receive a default value in those cases. */
required: boolean;
/** Indicates whether this parameter is **sensitive** or not. Unleash will not return sensitive parameters to API requests. It will instead use a number of asterisks to indicate that a value is set, e.g. "******". The number of asterisks does not correlate to the parameter's value. */
sensitive: boolean;
}
16 changes: 13 additions & 3 deletions frontend/src/openapi/models/addonSchema.ts
Expand Up @@ -5,14 +5,24 @@
*/
import type { AddonSchemaParameters } from './addonSchemaParameters';

/**
* An [addon](https://docs.getunleash.io/reference/addons) instance description. Contains data about what kind of provider it uses, whether it's enabled or not, what events it listens for, and more.
*/
export interface AddonSchema {
id?: number;
createdAt?: string | null;
/** The addon's unique identifier. */
id: number;
/** The addon provider, such as "webhook" or "slack". */
provider: string;
description?: string;
/** A description of the addon. `null` if no description exists. */
description: string | null;
/** Whether the addon is enabled or not. */
enabled: boolean;
/** Parameters for the addon provider. This object has different required and optional properties depending on the provider you choose. */
parameters: AddonSchemaParameters;
/** The event types that trigger this specific addon. */
events: string[];
/** The projects that this addon listens to events from. An empty list means it listens to events from **all** projects. */
projects?: string[];
/** The list of environments that this addon listens to events from. An empty list means it listens to events from **all** environments. */
environments?: string[];
}
5 changes: 4 additions & 1 deletion frontend/src/openapi/models/addonSchemaParameters.ts
Expand Up @@ -4,4 +4,7 @@
* See `gen:api` script in package.json
*/

export type AddonSchemaParameters = { [key: string]: any };
/**
* Parameters for the addon provider. This object has different required and optional properties depending on the provider you choose.
*/
export type AddonSchemaParameters = { [key: string]: unknown };
10 changes: 10 additions & 0 deletions frontend/src/openapi/models/addonTypeSchema.ts
Expand Up @@ -6,12 +6,22 @@
import type { TagTypeSchema } from './tagTypeSchema';
import type { AddonParameterSchema } from './addonParameterSchema';

/**
* An addon provider. Defines a specific addon type and what the end user must configure when creating a new addon of that type.
*/
export interface AddonTypeSchema {
/** The name of the addon type. When creating new addons, this goes in the payload's `type` field. */
name: string;
/** The addon type's name as it should be displayed in the admin UI. */
displayName: string;
/** A URL to where you can find more information about using this addon type. */
documentationUrl: string;
/** A description of the addon type. */
description: string;
/** A list of [Unleash tag types](https://docs.getunleash.io/reference/tags#tag-types) that this addon uses. These tags will be added to the Unleash instance when an addon of this type is created. */
tagTypes?: TagTypeSchema[];
/** The addon provider's parameters. Use these to configure an addon of this provider type. Items with `required: true` must be provided. */
parameters?: AddonParameterSchema[];
/** All the [event types](https://docs.getunleash.io/reference/api/legacy/unleash/admin/events#feature-toggle-events) that are available for this addon provider. */
events?: string[];
}
7 changes: 7 additions & 0 deletions frontend/src/openapi/models/addonsSchema.ts
Expand Up @@ -6,7 +6,14 @@
import type { AddonSchema } from './addonSchema';
import type { AddonTypeSchema } from './addonTypeSchema';

/**
* An object containing two things:
1. A list of all [addons](https://docs.getunleash.io/reference/addons) defined on this Unleash instance
2. A list of all addon providers defined on this instance
*/
export interface AddonsSchema {
/** All the addons that exist on this instance of Unleash. */
addons: AddonSchema[];
/** A list of all available addon providers, along with their parameters and descriptions. */
providers: AddonTypeSchema[];
}
6 changes: 6 additions & 0 deletions frontend/src/openapi/models/createFeatureStrategySchema.ts
Expand Up @@ -7,9 +7,15 @@ import type { ConstraintSchema } from './constraintSchema';
import type { ParametersSchema } from './parametersSchema';

export interface CreateFeatureStrategySchema {
/** The name or type of strategy */
name: string;
/** A descriptive title for the strategy */
title?: string | null;
/** The order of the strategy in the list */
sortOrder?: number;
/** A list of the constraints attached to the strategy */
constraints?: ConstraintSchema[];
/** An object containing the parameters for the strategy */
parameters?: ParametersSchema;
/** Ids of segments to use for this strategy */
segments?: number[];
Expand Down

0 comments on commit 169b2bd

Please sign in to comment.