mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-07 01:16:28 +02:00
fixed segments not being copied (#2105)
* fixed segments not being copied * fix fmt * bug fix * return segmentId[] when getting a feature strategy * do not return segments if they are not there * Update src/lib/services/feature-toggle-service.ts Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com> * fix lint * fix: more explicit column sorting and bug fix * update snapshot * rollback * add segment ids to feature strategies * bug fix Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com>
This commit is contained in:
parent
10eb500360
commit
64b8df7ee0
@ -12,6 +12,8 @@ import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmu
|
|||||||
import { getFeatureStrategyIcon } from 'utils/strategyNames';
|
import { getFeatureStrategyIcon } from 'utils/strategyNames';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { CopyButton } from './CopyButton/CopyButton';
|
import { CopyButton } from './CopyButton/CopyButton';
|
||||||
|
import { useSegments } from '../../../../hooks/api/getters/useSegments/useSegments';
|
||||||
|
import { IFeatureStrategyPayload } from '../../../../interfaces/strategy';
|
||||||
|
|
||||||
interface IFeatureStrategyEmptyProps {
|
interface IFeatureStrategyEmptyProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -65,6 +67,7 @@ export const FeatureStrategyEmpty = ({
|
|||||||
const { id, ...strategyCopy } = {
|
const { id, ...strategyCopy } = {
|
||||||
...strategy,
|
...strategy,
|
||||||
environment: environmentId,
|
environment: environmentId,
|
||||||
|
copyOf: strategy.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
return addStrategyToFeature(
|
return addStrategyToFeature(
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { AddToPhotos as CopyIcon, Lock } from '@mui/icons-material';
|
import { AddToPhotos as CopyIcon, Lock } from '@mui/icons-material';
|
||||||
import { IFeatureStrategy } from 'interfaces/strategy';
|
import { IFeatureStrategy, IFeatureStrategyPayload } from 'interfaces/strategy';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
import { IFeatureEnvironment } from 'interfaces/featureToggle';
|
||||||
import AccessContext from 'contexts/AccessContext';
|
import AccessContext from 'contexts/AccessContext';
|
||||||
@ -19,6 +19,7 @@ import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFe
|
|||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
|
import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import { useSegments } from '../../../../../../../../../../hooks/api/getters/useSegments/useSegments';
|
||||||
|
|
||||||
interface ICopyStrategyIconMenuProps {
|
interface ICopyStrategyIconMenuProps {
|
||||||
environments: IFeatureEnvironment['name'][];
|
environments: IFeatureEnvironment['name'][];
|
||||||
@ -31,6 +32,8 @@ export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const featureId = useRequiredPathParam('featureId');
|
const featureId = useRequiredPathParam('featureId');
|
||||||
|
const { segments } = useSegments(strategy.id);
|
||||||
|
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
const open = Boolean(anchorEl);
|
const open = Boolean(anchorEl);
|
||||||
const { addStrategyToFeature } = useFeatureStrategyApi();
|
const { addStrategyToFeature } = useFeatureStrategyApi();
|
||||||
@ -48,6 +51,7 @@ export const CopyStrategyIconMenu: VFC<ICopyStrategyIconMenuProps> = ({
|
|||||||
const { id, ...strategyCopy } = {
|
const { id, ...strategyCopy } = {
|
||||||
...strategy,
|
...strategy,
|
||||||
environment: environmentId,
|
environment: environmentId,
|
||||||
|
copyOf: strategy.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -19,6 +19,7 @@ export interface IFeatureStrategyPayload {
|
|||||||
name?: string;
|
name?: string;
|
||||||
constraints: IConstraint[];
|
constraints: IConstraint[];
|
||||||
parameters: IFeatureStrategyParameters;
|
parameters: IFeatureStrategyParameters;
|
||||||
|
copyOf?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IStrategy {
|
export interface IStrategy {
|
||||||
|
@ -101,7 +101,10 @@ export default class EnvironmentStore implements IEnvironmentStore {
|
|||||||
async getAll(query?: Object): Promise<IEnvironment[]> {
|
async getAll(query?: Object): Promise<IEnvironment[]> {
|
||||||
let qB = this.db<IEnvironmentsTable>(TABLE)
|
let qB = this.db<IEnvironmentsTable>(TABLE)
|
||||||
.select('*')
|
.select('*')
|
||||||
.orderBy('sort_order', 'created_at');
|
.orderBy([
|
||||||
|
{ column: 'sort_order', order: 'asc' },
|
||||||
|
{ column: 'created_at', order: 'asc' },
|
||||||
|
]);
|
||||||
if (query) {
|
if (query) {
|
||||||
qB = qB.where(query);
|
qB = qB.where(query);
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,10 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
feature_name: featureName,
|
feature_name: featureName,
|
||||||
environment,
|
environment,
|
||||||
})
|
})
|
||||||
.orderBy('sort_order', 'created_at');
|
.orderBy([
|
||||||
|
{ column: 'sort_order', order: 'asc' },
|
||||||
|
{ column: 'created_at', order: 'asc' },
|
||||||
|
]);
|
||||||
stopTimer();
|
stopTimer();
|
||||||
return rows.map(mapRow);
|
return rows.map(mapRow);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,9 @@ export const createFeatureStrategySchema = {
|
|||||||
$ref: '#/components/schemas/constraintSchema',
|
$ref: '#/components/schemas/constraintSchema',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
copyOf: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
$ref: '#/components/schemas/parametersSchema',
|
$ref: '#/components/schemas/parametersSchema',
|
||||||
},
|
},
|
||||||
|
@ -21,6 +21,8 @@ export const publicSignupTokenSchema = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
url: {
|
url: {
|
||||||
|
description:
|
||||||
|
'The public signup link for the token. Users who follow this link will be taken to a signup page where they can create an Unleash user.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
@ -43,12 +45,15 @@ export const publicSignupTokenSchema = {
|
|||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
description: 'Array of users that have signed up using the token',
|
||||||
items: {
|
items: {
|
||||||
$ref: '#/components/schemas/userSchema',
|
$ref: '#/components/schemas/userSchema',
|
||||||
},
|
},
|
||||||
nullable: true,
|
nullable: true,
|
||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
|
description:
|
||||||
|
'Users who sign up using this token will be given this role.',
|
||||||
$ref: '#/components/schemas/roleSchema',
|
$ref: '#/components/schemas/roleSchema',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -39,6 +39,7 @@ import { FeatureEnvironmentSchema } from '../../../openapi/spec/feature-environm
|
|||||||
import { SetStrategySortOrderSchema } from '../../../openapi/spec/set-strategy-sort-order-schema';
|
import { SetStrategySortOrderSchema } from '../../../openapi/spec/set-strategy-sort-order-schema';
|
||||||
|
|
||||||
import { emptyResponse } from '../../../openapi/util/standard-responses';
|
import { emptyResponse } from '../../../openapi/util/standard-responses';
|
||||||
|
import { SegmentService } from '../../../services/segment-service';
|
||||||
|
|
||||||
interface FeatureStrategyParams {
|
interface FeatureStrategyParams {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -68,7 +69,10 @@ const PATH_STRATEGY = `${PATH_STRATEGIES}/:strategyId`;
|
|||||||
|
|
||||||
type ProjectFeaturesServices = Pick<
|
type ProjectFeaturesServices = Pick<
|
||||||
IUnleashServices,
|
IUnleashServices,
|
||||||
'featureToggleServiceV2' | 'projectHealthService' | 'openApiService'
|
| 'featureToggleServiceV2'
|
||||||
|
| 'projectHealthService'
|
||||||
|
| 'openApiService'
|
||||||
|
| 'segmentService'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export default class ProjectFeaturesController extends Controller {
|
export default class ProjectFeaturesController extends Controller {
|
||||||
@ -76,15 +80,22 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
|
|
||||||
private openApiService: OpenApiService;
|
private openApiService: OpenApiService;
|
||||||
|
|
||||||
|
private segmentService: SegmentService;
|
||||||
|
|
||||||
private readonly logger: Logger;
|
private readonly logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
{ featureToggleServiceV2, openApiService }: ProjectFeaturesServices,
|
{
|
||||||
|
featureToggleServiceV2,
|
||||||
|
openApiService,
|
||||||
|
segmentService,
|
||||||
|
}: ProjectFeaturesServices,
|
||||||
) {
|
) {
|
||||||
super(config);
|
super(config);
|
||||||
this.featureService = featureToggleServiceV2;
|
this.featureService = featureToggleServiceV2;
|
||||||
this.openApiService = openApiService;
|
this.openApiService = openApiService;
|
||||||
|
this.segmentService = segmentService;
|
||||||
this.logger = config.getLogger('/admin-api/project/features.ts');
|
this.logger = config.getLogger('/admin-api/project/features.ts');
|
||||||
|
|
||||||
this.route({
|
this.route({
|
||||||
@ -557,13 +568,29 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
res: Response<FeatureStrategySchema>,
|
res: Response<FeatureStrategySchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { projectId, featureName, environment } = req.params;
|
const { projectId, featureName, environment } = req.params;
|
||||||
|
const { copyOf, ...strategyConfig } = req.body;
|
||||||
|
|
||||||
const userName = extractUsername(req);
|
const userName = extractUsername(req);
|
||||||
const strategy = await this.featureService.createStrategy(
|
const strategy = await this.featureService.createStrategy(
|
||||||
req.body,
|
strategyConfig,
|
||||||
{ environment, projectId, featureName },
|
{ environment, projectId, featureName },
|
||||||
userName,
|
userName,
|
||||||
);
|
);
|
||||||
res.status(200).json(strategy);
|
|
||||||
|
if (copyOf) {
|
||||||
|
this.logger.info(
|
||||||
|
`Cloning segments from: strategyId=${copyOf} to: strategyId=${strategy.id} `,
|
||||||
|
);
|
||||||
|
await this.segmentService.cloneStrategySegments(
|
||||||
|
copyOf,
|
||||||
|
strategy.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedStrategy = await this.featureService.getStrategy(
|
||||||
|
strategy.id,
|
||||||
|
);
|
||||||
|
res.status(200).json(updatedStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFeatureStrategies(
|
async getFeatureStrategies(
|
||||||
|
@ -53,6 +53,7 @@ export class PublicInviteController extends Controller {
|
|||||||
openApiService.validPath({
|
openApiService.validPath({
|
||||||
tags: ['Public signup tokens'],
|
tags: ['Public signup tokens'],
|
||||||
operationId: 'validatePublicSignupToken',
|
operationId: 'validatePublicSignupToken',
|
||||||
|
summary: `Validates a public signup token exists, has not expired and is enabled`,
|
||||||
responses: {
|
responses: {
|
||||||
200: emptyResponse,
|
200: emptyResponse,
|
||||||
...getStandardResponses(400),
|
...getStandardResponses(400),
|
||||||
@ -70,6 +71,8 @@ export class PublicInviteController extends Controller {
|
|||||||
openApiService.validPath({
|
openApiService.validPath({
|
||||||
tags: ['Public signup tokens'],
|
tags: ['Public signup tokens'],
|
||||||
operationId: 'addPublicSignupTokenUser',
|
operationId: 'addPublicSignupTokenUser',
|
||||||
|
summary:
|
||||||
|
'Create a user with the "viewer" root role and link them to a signup token',
|
||||||
requestBody: createRequestSchema('createInvitedUserSchema'),
|
requestBody: createRequestSchema('createInvitedUserSchema'),
|
||||||
responses: {
|
responses: {
|
||||||
200: createResponseSchema('userSchema'),
|
200: createResponseSchema('userSchema'),
|
||||||
|
@ -45,6 +45,7 @@ import {
|
|||||||
IFeatureOverview,
|
IFeatureOverview,
|
||||||
IFeatureStrategy,
|
IFeatureStrategy,
|
||||||
IFeatureToggleQuery,
|
IFeatureToggleQuery,
|
||||||
|
ISegment,
|
||||||
IStrategyConfig,
|
IStrategyConfig,
|
||||||
IVariant,
|
IVariant,
|
||||||
WeightType,
|
WeightType,
|
||||||
@ -283,12 +284,14 @@ class FeatureToggleService {
|
|||||||
|
|
||||||
featureStrategyToPublic(
|
featureStrategyToPublic(
|
||||||
featureStrategy: IFeatureStrategy,
|
featureStrategy: IFeatureStrategy,
|
||||||
|
segments: ISegment[] = [],
|
||||||
): Saved<IStrategyConfig> {
|
): Saved<IStrategyConfig> {
|
||||||
return {
|
return {
|
||||||
id: featureStrategy.id,
|
id: featureStrategy.id,
|
||||||
name: featureStrategy.strategyName,
|
name: featureStrategy.strategyName,
|
||||||
constraints: featureStrategy.constraints || [],
|
constraints: featureStrategy.constraints || [],
|
||||||
parameters: featureStrategy.parameters,
|
parameters: featureStrategy.parameters,
|
||||||
|
segments: segments.map((segment) => segment.id) ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,7 +333,13 @@ class FeatureToggleService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const tags = await this.tagStore.getAllTagsForFeature(featureName);
|
const tags = await this.tagStore.getAllTagsForFeature(featureName);
|
||||||
const strategy = this.featureStrategyToPublic(newFeatureStrategy);
|
const segments = await this.segmentService.getByStrategy(
|
||||||
|
newFeatureStrategy.id,
|
||||||
|
);
|
||||||
|
const strategy = this.featureStrategyToPublic(
|
||||||
|
newFeatureStrategy,
|
||||||
|
segments,
|
||||||
|
);
|
||||||
await this.eventStore.store(
|
await this.eventStore.store(
|
||||||
new FeatureStrategyAddEvent({
|
new FeatureStrategyAddEvent({
|
||||||
project: projectId,
|
project: projectId,
|
||||||
@ -385,10 +394,17 @@ class FeatureToggleService {
|
|||||||
updates,
|
updates,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const segments = await this.segmentService.getByStrategy(
|
||||||
|
strategy.id,
|
||||||
|
);
|
||||||
|
|
||||||
// Store event!
|
// Store event!
|
||||||
const tags = await this.tagStore.getAllTagsForFeature(featureName);
|
const tags = await this.tagStore.getAllTagsForFeature(featureName);
|
||||||
const data = this.featureStrategyToPublic(strategy);
|
const data = this.featureStrategyToPublic(strategy, segments);
|
||||||
const preData = this.featureStrategyToPublic(existingStrategy);
|
const preData = this.featureStrategyToPublic(
|
||||||
|
existingStrategy,
|
||||||
|
segments,
|
||||||
|
);
|
||||||
await this.eventStore.store(
|
await this.eventStore.store(
|
||||||
new FeatureStrategyUpdateEvent({
|
new FeatureStrategyUpdateEvent({
|
||||||
project: projectId,
|
project: projectId,
|
||||||
@ -424,8 +440,14 @@ class FeatureToggleService {
|
|||||||
existingStrategy,
|
existingStrategy,
|
||||||
);
|
);
|
||||||
const tags = await this.tagStore.getAllTagsForFeature(featureName);
|
const tags = await this.tagStore.getAllTagsForFeature(featureName);
|
||||||
const data = this.featureStrategyToPublic(strategy);
|
const segments = await this.segmentService.getByStrategy(
|
||||||
const preData = this.featureStrategyToPublic(existingStrategy);
|
strategy.id,
|
||||||
|
);
|
||||||
|
const data = this.featureStrategyToPublic(strategy, segments);
|
||||||
|
const preData = this.featureStrategyToPublic(
|
||||||
|
existingStrategy,
|
||||||
|
segments,
|
||||||
|
);
|
||||||
await this.eventStore.store(
|
await this.eventStore.store(
|
||||||
new FeatureStrategyUpdateEvent({
|
new FeatureStrategyUpdateEvent({
|
||||||
featureName,
|
featureName,
|
||||||
@ -488,6 +510,7 @@ class FeatureToggleService {
|
|||||||
featureName: string,
|
featureName: string,
|
||||||
environment: string = DEFAULT_ENV,
|
environment: string = DEFAULT_ENV,
|
||||||
): Promise<Saved<IStrategyConfig>[]> {
|
): Promise<Saved<IStrategyConfig>[]> {
|
||||||
|
this.logger.debug('getStrategiesForEnvironment');
|
||||||
const hasEnv = await this.featureEnvironmentStore.featureHasEnvironment(
|
const hasEnv = await this.featureEnvironmentStore.featureHasEnvironment(
|
||||||
environment,
|
environment,
|
||||||
featureName,
|
featureName,
|
||||||
@ -499,13 +522,22 @@ class FeatureToggleService {
|
|||||||
featureName,
|
featureName,
|
||||||
environment,
|
environment,
|
||||||
);
|
);
|
||||||
return featureStrategies.map((strat) => ({
|
const result = [];
|
||||||
|
for (const strat of featureStrategies) {
|
||||||
|
const segments =
|
||||||
|
(await this.segmentService.getByStrategy(strat.id)).map(
|
||||||
|
(segment) => segment.id,
|
||||||
|
) ?? [];
|
||||||
|
result.push({
|
||||||
id: strat.id,
|
id: strat.id,
|
||||||
name: strat.strategyName,
|
name: strat.strategyName,
|
||||||
constraints: strat.constraints,
|
constraints: strat.constraints,
|
||||||
parameters: strat.parameters,
|
parameters: strat.parameters,
|
||||||
sortOrder: strat.sortOrder,
|
sortOrder: strat.sortOrder,
|
||||||
}));
|
segments,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
throw new NotFoundError(
|
throw new NotFoundError(
|
||||||
`Feature ${featureName} does not have environment ${environment}`,
|
`Feature ${featureName} does not have environment ${environment}`,
|
||||||
@ -727,12 +759,23 @@ class FeatureToggleService {
|
|||||||
const strategy = await this.featureStrategiesStore.getStrategyById(
|
const strategy = await this.featureStrategiesStore.getStrategyById(
|
||||||
strategyId,
|
strategyId,
|
||||||
);
|
);
|
||||||
return {
|
|
||||||
|
const segments = await this.segmentService.getByStrategy(strategyId);
|
||||||
|
let result: Saved<IStrategyConfig> = {
|
||||||
id: strategy.id,
|
id: strategy.id,
|
||||||
name: strategy.strategyName,
|
name: strategy.strategyName,
|
||||||
constraints: strategy.constraints || [],
|
constraints: strategy.constraints || [],
|
||||||
parameters: strategy.parameters,
|
parameters: strategy.parameters,
|
||||||
|
segments: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (segments && segments.length > 0) {
|
||||||
|
result = {
|
||||||
|
...result,
|
||||||
|
segments: segments.map((segment) => segment.id),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEnvironmentInfo(
|
async getEnvironmentInfo(
|
||||||
|
@ -124,7 +124,6 @@ export class SegmentService {
|
|||||||
const sourceStrategySegments = await this.getByStrategy(
|
const sourceStrategySegments = await this.getByStrategy(
|
||||||
sourceStrategyId,
|
sourceStrategyId,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
sourceStrategySegments.map((sourceStrategySegment) => {
|
sourceStrategySegments.map((sourceStrategySegment) => {
|
||||||
return this.addToStrategy(
|
return this.addToStrategy(
|
||||||
|
@ -709,6 +709,9 @@ exports[`should serve the OpenAPI spec 1`] = `
|
|||||||
},
|
},
|
||||||
"type": "array",
|
"type": "array",
|
||||||
},
|
},
|
||||||
|
"copyOf": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
@ -2483,14 +2486,17 @@ exports[`should serve the OpenAPI spec 1`] = `
|
|||||||
},
|
},
|
||||||
"role": {
|
"role": {
|
||||||
"$ref": "#/components/schemas/roleSchema",
|
"$ref": "#/components/schemas/roleSchema",
|
||||||
|
"description": "Users who sign up using this token will be given this role.",
|
||||||
},
|
},
|
||||||
"secret": {
|
"secret": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
|
"description": "The public signup link for the token. Users who follow this link will be taken to a signup page where they can create an Unleash user.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
"users": {
|
"users": {
|
||||||
|
"description": "Array of users that have signed up using the token",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/userSchema",
|
"$ref": "#/components/schemas/userSchema",
|
||||||
},
|
},
|
||||||
@ -7472,6 +7478,7 @@ If the provided project does not exist, the list of events will be empty.",
|
|||||||
"description": "The provided resource can not be created or updated because it would conflict with the current state of the resource or with an already existing resource, respectively.",
|
"description": "The provided resource can not be created or updated because it would conflict with the current state of the resource or with an already existing resource, respectively.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"summary": "Create a user with the "viewer" root role and link them to a signup token",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Public signup tokens",
|
"Public signup tokens",
|
||||||
],
|
],
|
||||||
@ -7498,6 +7505,7 @@ If the provided project does not exist, the list of events will be empty.",
|
|||||||
"description": "The request data does not match what we expect.",
|
"description": "The request data does not match what we expect.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"summary": "Validates a public signup token exists, has not expired and is enabled",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Public signup tokens",
|
"Public signup tokens",
|
||||||
],
|
],
|
||||||
|
Loading…
Reference in New Issue
Block a user