Merge branch 'master' into telemetry

This commit is contained in:
Simon Larsen
2023-11-17 16:36:54 +00:00
57 changed files with 4954 additions and 1486 deletions

2
.gitignore vendored
View File

@@ -13,7 +13,7 @@ node_modules
.idea
# testing
/coverage
**/coverage
# production
/build

View File

@@ -187,6 +187,7 @@ const Settings: FunctionComponent = (): ReactElement => {
<Statusbubble
text={'Connected'}
color={Green}
shouldAnimate={true}
/>
);
}
@@ -195,6 +196,7 @@ const Settings: FunctionComponent = (): ReactElement => {
<Statusbubble
text={'Disconnected'}
color={Red}
shouldAnimate={false}
/>
);
},

View File

@@ -108,7 +108,7 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
monitorCriteriaInstance.data = {
id: ObjectID.generate().toString(),
monitorStatusId: arg.monitorStatusId,
filterCondition: FilterCondition.All,
filterCondition: FilterCondition.Any,
filters: [
{
checkOn: CheckOn.IsOnline,

View File

@@ -280,11 +280,32 @@ export default class API {
}
try {
const finalHeaders: Dictionary<string> = {
...apiHeaders,
...headers,
};
let finalBody:
| JSONObject
| JSONArray
| URLSearchParams
| undefined = data;
// if content-type is form-url-encoded, then stringify the data
if (
finalHeaders['Content-Type'] ===
'application/x-www-form-urlencoded' &&
data
) {
finalBody = new URLSearchParams(data as Dictionary<string>);
}
const result: AxiosResponse = await axios({
method: method,
url: url.toString(),
headers: { ...apiHeaders, ...headers },
data,
headers: finalHeaders,
data: finalBody,
});
result.headers = await this.onResponseSuccessHeaders(

View File

@@ -4,7 +4,6 @@ import OneUptimeDate from 'Common/Types/Date';
import APIException from 'Common/Types/Exception/ApiException';
import BadDataException from 'Common/Types/Exception/BadDataException';
import ObjectID from 'Common/Types/ObjectID';
import Typeof from 'Common/Types/Typeof';
import logger from '../Utils/Logger';
import Stripe from 'stripe';
import { BillingPrivateKey, IsBillingEnabled } from '../EnvironmentConfig';
@@ -15,6 +14,7 @@ import SubscriptionStatus, {
import BaseService from './BaseService';
import Email from 'Common/Types/Email';
import Dictionary from 'Common/Types/Dictionary';
import Errors from '../Utils/Errors';
export type SubscriptionItem = Stripe.SubscriptionItem;
@@ -54,10 +54,9 @@ export class BillingService extends BaseService {
}): Promise<string> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
const customer: Stripe.Response<Stripe.Customer> =
await this.stripe.customers.create({
name: data.name,
@@ -66,7 +65,6 @@ export class BillingService extends BaseService {
id: data.id.toString(),
},
});
return customer.id;
}
@@ -76,7 +74,7 @@ export class BillingService extends BaseService {
): Promise<void> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
@@ -86,7 +84,7 @@ export class BillingService extends BaseService {
public async deleteCustomer(id: string): Promise<void> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
@@ -191,19 +189,21 @@ export class BillingService extends BaseService {
}> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
let trialDate: Date | null = null;
if (typeof data.trial === Typeof.Boolean) {
trialDate = OneUptimeDate.getSomeDaysAfter(
data.plan.getTrialPeriod()
);
}
if (data.trial instanceof Date) {
if (typeof data.trial === 'boolean') {
if (data.trial) {
trialDate = OneUptimeDate.getSomeDaysAfter(
data.plan.getTrialPeriod()
);
} else {
trialDate = null;
}
} else if (data.trial instanceof Date) {
trialDate = data.trial;
}
@@ -260,7 +260,7 @@ export class BillingService extends BaseService {
): Promise<void> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
@@ -268,9 +268,10 @@ export class BillingService extends BaseService {
await this.stripe.subscriptions.retrieve(subscriptionId);
if (!subscription) {
throw new BadDataException('Subscription not found');
throw new BadDataException(
Errors.BillingService.SUBSCRIPTION_NOT_FOUND
);
}
if (subscription.status === 'canceled') {
// subscription is canceled.
return;
@@ -280,7 +281,9 @@ export class BillingService extends BaseService {
subscription.items.data[0]?.id;
if (!subscriptionItemId) {
throw new BadDataException('Subscription Item not found');
throw new BadDataException(
Errors.BillingService.SUBSCRIPTION_ITEM_NOT_FOUND
);
}
await this.stripe.subscriptionItems.update(subscriptionItemId, {
@@ -295,7 +298,7 @@ export class BillingService extends BaseService {
): Promise<void> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
@@ -305,7 +308,9 @@ export class BillingService extends BaseService {
);
if (!subscription) {
throw new BadDataException('Subscription not found');
throw new BadDataException(
Errors.BillingService.SUBSCRIPTION_NOT_FOUND
);
}
// check if this pricing exists
@@ -324,7 +329,9 @@ export class BillingService extends BaseService {
})?.id;
if (!subscriptionItemId) {
throw new BadDataException('Subscription Item not found');
throw new BadDataException(
Errors.BillingService.SUBSCRIPTION_ITEM_NOT_FOUND
);
}
// use stripe usage based api to update the quantity.
@@ -357,7 +364,7 @@ export class BillingService extends BaseService {
public async isPromoCodeValid(promoCode: string): Promise<boolean> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
try {
@@ -365,13 +372,16 @@ export class BillingService extends BaseService {
await this.stripe.coupons.retrieve(promoCode);
if (!promoCodeResponse) {
throw new BadDataException('Promo code not found');
throw new BadDataException(
Errors.BillingService.PROMO_CODE_NOT_FOUND
);
}
return promoCodeResponse.valid;
} catch (err) {
throw new BadDataException(
(err as Error).message || 'Invalid promo code'
(err as Error).message ||
Errors.BillingService.PROMO_CODE_INVALID
);
}
}
@@ -383,7 +393,7 @@ export class BillingService extends BaseService {
): Promise<void> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
@@ -391,7 +401,9 @@ export class BillingService extends BaseService {
await this.stripe.subscriptions.retrieve(subscriptionId);
if (!subscription) {
throw new BadDataException('Subscription not found');
throw new BadDataException(
Errors.BillingService.SUBSCRIPTION_NOT_FOUND
);
}
if (subscription.status === 'canceled') {
@@ -418,7 +430,7 @@ export class BillingService extends BaseService {
): Promise<Array<SubscriptionItem>> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
@@ -426,7 +438,9 @@ export class BillingService extends BaseService {
await this.stripe.subscriptions.retrieve(subscriptionId);
if (!subscription) {
throw new BadDataException('Subscription not found');
throw new BadDataException(
Errors.BillingService.SUBSCRIPTION_NOT_FOUND
);
}
return subscription.items.data;
@@ -450,10 +464,10 @@ export class BillingService extends BaseService {
logger.info(data);
if (!this.isBillingEnabled()) {
logger.info('Billing not enabled');
logger.info(Errors.BillingService.BILLING_NOT_ENABLED);
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
@@ -464,8 +478,10 @@ export class BillingService extends BaseService {
logger.info(subscription);
if (!subscription) {
logger.info('Subscription not found');
throw new BadDataException('Subscription not found');
logger.info(Errors.BillingService.SUBSCRIPTION_NOT_FOUND);
throw new BadDataException(
Errors.BillingService.SUBSCRIPTION_NOT_FOUND
);
}
logger.info('Subscription status');
@@ -481,7 +497,7 @@ export class BillingService extends BaseService {
logger.info('No payment methods');
throw new BadDataException(
'No payment methods added. Please add your card to this project to change your plan'
Errors.BillingService.NO_PAYMENTS_METHODS
);
}
@@ -536,7 +552,7 @@ export class BillingService extends BaseService {
): Promise<void> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
@@ -545,7 +561,7 @@ export class BillingService extends BaseService {
if (paymentMethods.length === 1) {
throw new BadDataException(
"There's only one payment method associated with this account. It cannot be deleted. To delete this payment method please add more payment methods to your account."
Errors.BillingService.MIN_REQUIRED_PAYMENT_METHOD_NOT_MET
);
}
@@ -576,7 +592,7 @@ export class BillingService extends BaseService {
): Promise<Array<PaymentMethod>> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
const paymentMethods: Array<PaymentMethod> = [];
@@ -671,9 +687,7 @@ export class BillingService extends BaseService {
});
if (!setupIntent.client_secret) {
throw new APIException(
'client_secret not returned by payment provider.'
);
throw new APIException(Errors.BillingService.CLIENT_SECRET_MISSING);
}
return setupIntent.client_secret;
@@ -682,7 +696,7 @@ export class BillingService extends BaseService {
public async cancelSubscription(subscriptionId: string): Promise<void> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
try {
@@ -706,7 +720,7 @@ export class BillingService extends BaseService {
): Promise<Stripe.Subscription> {
if (!this.isBillingEnabled()) {
throw new BadDataException(
'Billing is not enabled for this server.'
Errors.BillingService.BILLING_NOT_ENABLED
);
}
@@ -748,7 +762,7 @@ export class BillingService extends BaseService {
});
if (!invoice || !invoice.id) {
throw new APIException('Invoice not generated.');
throw new APIException(Errors.BillingService.INVOICE_NOT_GENERATED);
}
await this.stripe.invoiceItems.create({
@@ -787,7 +801,7 @@ export class BillingService extends BaseService {
if (paymentMethods.length === 0) {
throw new BadDataException(
'Payment Method not added. Please go to Project Settings > Billing and add a payment method.'
Errors.BillingService.NO_PAYMENTS_METHODS
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,161 @@
import { Stripe } from 'stripe';
import { faker } from '@faker-js/faker';
import Email from 'Common/Types/Email';
import ObjectID from 'Common/Types/ObjectID';
import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
import { BillingService } from '../../../Services/BillingService';
import {
CustomerData,
Subscription,
MeteredSubscription,
ChangePlan,
CouponData,
} from '../../TestingUtils/Services/Types';
/// @dev consider modifyfing the EnvirontmentConfig to use functions instead of constants so that we can mock them
const mockIsBillingEnabled: Function = (value: boolean): BillingService => {
jest.resetModules();
jest.doMock('../../../EnvironmentConfig', () => {
return {
IsBillingEnabled: value,
};
});
const { BillingService } = require('../../../Services/BillingService');
return new BillingService();
};
const getStripeCustomer: Function = (id?: string): Stripe.Customer => {
id = id || faker.datatype.uuid();
return {
id,
object: 'customer',
balance: faker.datatype.number(),
created: 1,
default_source: null,
description: null,
email: null,
invoice_settings: {
custom_fields: null,
default_payment_method: null,
footer: null,
rendering_options: null,
},
livemode: true,
metadata: {},
shipping: null,
};
};
const getStripeSubscription: Function = (): Stripe.Subscription => {
return {
id: faker.datatype.uuid(),
items: {
data: [
{
id: faker.datatype.uuid(),
// @ts-ignore
price: { id: faker.datatype.uuid() },
},
],
},
status: 'active',
customer: getStripeCustomer(),
};
};
const getSubscriptionPlanData: Function = (): SubscriptionPlan => {
return new SubscriptionPlan(
faker.datatype.uuid(), // monthlyPlanId
faker.datatype.uuid(), // yearlyPlanId
faker.commerce.productName(), // name
faker.datatype.number(), // monthlySubscriptionAmountInUSD
faker.datatype.number({ min: 1, max: 100 }), // yearlySubscriptionAmountInUSD
faker.datatype.number({ min: 1, max: 100 }), // order
faker.datatype.number({ min: 1, max: 100 }) // trial period days
);
};
const getStripeInvoice: Function = (): Stripe.Invoice => {
// @ts-ignore
return {
id: faker.datatype.uuid(),
amount_due: faker.datatype.number(),
currency: 'usd',
customer: faker.datatype.uuid(),
subscription: faker.datatype.uuid(),
status: 'paid',
};
};
const getCustomerData: Function = (id?: ObjectID): CustomerData => {
return {
id: id || new ObjectID('customer_id'),
name: 'John Doe',
email: new Email('test@example.com'),
};
};
const getSubscriptionData: Function = (id?: ObjectID): Subscription => {
return {
projectId: id || new ObjectID('project_id'),
customerId: 'cust_123',
serverMeteredPlans: [],
trialDate: new Date(),
};
};
const getMeteredSubscription: Function = (
subscriptionPlan: SubscriptionPlan,
id?: ObjectID
): MeteredSubscription => {
return {
projectId: id || new ObjectID('project_id'),
customerId: 'cust_123',
serverMeteredPlans: [],
plan: subscriptionPlan,
quantity: 1,
isYearly: false,
trial: true,
};
};
const getChangePlanData: Function = (
subscriptionPlan: SubscriptionPlan,
id?: ObjectID
): ChangePlan => {
return {
projectId: id || new ObjectID('project_id'),
subscriptionId: 'sub_123',
meteredSubscriptionId: 'sub_456',
serverMeteredPlans: [],
newPlan: subscriptionPlan,
quantity: 1,
isYearly: false,
};
};
const getCouponData: Function = (): CouponData => {
return {
name: 'TESTCOUPON',
metadata: { description: 'Test coupon' },
percentOff: 10,
durationInMonths: 3,
maxRedemptions: 100,
};
};
export {
mockIsBillingEnabled,
getStripeCustomer,
getStripeSubscription,
getSubscriptionPlanData,
getCustomerData,
getSubscriptionData,
getMeteredSubscription,
getChangePlanData,
getCouponData,
getStripeInvoice,
};

View File

@@ -0,0 +1,57 @@
import Email from 'Common/Types/Email';
import ObjectID from 'Common/Types/ObjectID';
import ServerMeteredPlan from '../../../Types/Billing/MeteredPlan/ServerMeteredPlan';
import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
import { PaymentMethod } from '../../../Services/BillingService';
import Dictionary from 'Common/Types/Dictionary';
export type CustomerData = {
id: ObjectID;
name: string;
email: Email;
};
export type CouponData = {
name: string;
metadata?: Dictionary<string> | undefined;
percentOff: number;
durationInMonths: number;
maxRedemptions: number;
};
export type Subscription = {
projectId: ObjectID;
customerId: string;
serverMeteredPlans: Array<typeof ServerMeteredPlan>;
promoCode?: string;
defaultPaymentMethodId?: string;
trialDate: Date;
};
export type MeteredSubscription = {
projectId: ObjectID;
customerId: string;
serverMeteredPlans: Array<typeof ServerMeteredPlan>;
plan: SubscriptionPlan;
quantity: number;
isYearly: boolean;
trial: boolean | Date | undefined;
defaultPaymentMethodId?: string | undefined;
promoCode?: string | undefined;
};
export type ChangePlan = {
projectId: ObjectID;
subscriptionId: string;
meteredSubscriptionId: string;
serverMeteredPlans: Array<typeof ServerMeteredPlan>;
newPlan: SubscriptionPlan;
quantity: number;
isYearly: boolean;
endTrialAt?: Date | undefined;
};
export type PaymentMethodsResponse = {
data: PaymentMethod[];
defaultPaymentMethodId?: string | undefined;
};

View File

@@ -0,0 +1,16 @@
import * as mock from 'jest-mock-extended';
let mockStripe: jest.Mocked<Stripe>;
jest.mock('stripe', () => {
mockStripe = mock.mockDeep<Stripe>();
return jest.fn(() => {
return mockStripe;
});
});
// import libraries to mock (we do it here because of hoisting)
import Stripe from 'stripe';
// return the mocked library and the library itself
export { mockStripe, Stripe };

View File

@@ -0,0 +1,16 @@
export default {
BillingService: {
BILLING_NOT_ENABLED: 'Billing is not enabled for this server.',
CLIENT_SECRET_MISSING:
'client_secret not returned by payment provider.',
INVOICE_NOT_GENERATED: 'Invoice not generated.',
MIN_REQUIRED_PAYMENT_METHOD_NOT_MET:
"There's only one payment method associated with this account. It cannot be deleted. To delete this payment method please add more payment methods to your account.",
NO_PAYMENTS_METHODS:
'Payment Method not added. Please go to Project Settings > Billing and add a payment method.',
PROMO_CODE_NOT_FOUND: 'Promo code not found',
PROMO_CODE_INVALID: 'Invalid promo code',
SUBSCRIPTION_ITEM_NOT_FOUND: 'Subscription item not found.',
SUBSCRIPTION_NOT_FOUND: 'Subscription not found.',
},
};

View File

@@ -12,8 +12,11 @@
".(ts|tsx)": "ts-jest"
},
"testEnvironment": "node",
"collectCoverage": true,
"coverageReporters": ["text"],
"collectCoverage": false,
"transformIgnorePatterns": [
"/node_modules/(?!Common).+\\.js$"
],
"coverageReporters": ["text", "lcov"],
"testRegex": "./Tests/(.*).test.ts",
"collectCoverageFrom": ["./**/*.(tsx||ts)"],
"coverageThreshold": {

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@
"scripts": {
"compile": "tsc",
"test": "jest --detectOpenHandles",
"coverage": "jest --detectOpenHandles --coverage",
"debug:test": "cd .. && export $(grep -v '^#' config.env | xargs) && cd CommonServer && node --inspect node_modules/.bin/jest --runInBand ./Tests --detectOpenHandles"
},
"author": "",
@@ -60,6 +61,7 @@
"@types/jsonwebtoken": "^8.5.9",
"@types/node": "^17.0.22",
"jest": "^27.5.1",
"ts-jest": "^27.1.4"
"ts-jest": "^27.1.4",
"jest-mock-extended": "^3.0.5"
}
}

View File

@@ -11,9 +11,12 @@
"transform": {
".(ts|tsx)": "ts-jest"
},
"transformIgnorePatterns": [
"/node_modules/(?!Common).+\\.js$"
],
"testEnvironment": "jsdom",
"collectCoverage": true,
"coverageReporters": ["text", "html"],
"collectCoverage": false,
"coverageReporters": ["text", "lcov", "html"],
"testRegex": "./src/Tests/(.*).test.(tsx||ts)",
"collectCoverageFrom": ["./**/*.(tsx||ts)"],
"coverageThreshold": {

View File

@@ -143,7 +143,7 @@ const EventItem: FunctionComponent<ComponentProps> = (
{props.eventResourcesAffected &&
props.eventResourcesAffected?.length > 0 ? (
<div key={0}>
<div className="flex space-x-1">
<div className="flex flex-wrap gap-y-4 space-x-1">
<div className="text-sm text-gray-400 mr-3 mt-1">
Affected resources
</div>

View File

@@ -35,6 +35,7 @@ export interface ComponentProps {
onClick?: (() => void) | undefined;
type?: IconType | undefined;
style?: React.CSSProperties | undefined;
'data-testid'?: string;
}
const Icon: FunctionComponent<ComponentProps> = ({
size = SizeProp.Regular,
@@ -45,6 +46,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
onClick,
type,
style,
'data-testid': dataTestId,
}: ComponentProps): ReactElement => {
let sizeClassName: string = '';
if (
@@ -115,6 +117,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
strokeWidth="1.5"
stroke="currentColor"
aria-hidden="true"
data-testid={dataTestId}
>
{children}
</svg>

View File

@@ -14,6 +14,7 @@ export interface ComponentProps {
className?: string | undefined;
alt?: string | undefined;
style?: React.CSSProperties | undefined;
'data-testid'?: string;
}
export class ImageFunctions {
@@ -36,6 +37,7 @@ const Image: FunctionComponent<ComponentProps> = (
onClick={() => {
props.onClick && props.onClick();
}}
data-testid={props['data-testid']}
alt={props.alt}
src={url}
height={props.height}

View File

@@ -26,7 +26,9 @@ const ProbeElement: FunctionComponent<ComponentProps> = (
if (!probe) {
return (
<div className="flex">
<div className="bold">No probe found.</div>
<div className="bold" data-testid="probe-not-found">
No probe found.
</div>
</div>
);
}
@@ -37,6 +39,7 @@ const ProbeElement: FunctionComponent<ComponentProps> = (
{props.probe?.iconFileId && (
<Image
className="h-6 w-6 rounded-full"
data-testid="probe-image"
imageUrl={URL.fromString(FILE_URL.toString()).addRoute(
'/image/' + props.probe?.iconFileId.toString()
)}
@@ -45,6 +48,7 @@ const ProbeElement: FunctionComponent<ComponentProps> = (
)}
{!props.probe?.iconFileId && (
<Icon
data-testid="probe-icon"
icon={IconProp.Signal}
className="text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-0.5 mt-0.5 h-6 w-6"
/>
@@ -52,7 +56,7 @@ const ProbeElement: FunctionComponent<ComponentProps> = (
</div>
<div className="mt-1 mr-1 ml-3">
<div>
<span>{`${
<span data-testid="probe-name">{`${
(probe['name']?.toString() as string) || ''
}`}</span>{' '}
</div>

View File

@@ -35,14 +35,15 @@ const ProgressBar: FunctionComponent<ComponentProps> = (
return (
<div className="w-full h-4 mb-4 bg-gray-200 rounded-full dark:bg-gray-700">
<div
data-testid="progress-bar"
className="h-4 bg-indigo-600 rounded-full dark:bg-indigo-500"
style={{ width: percent + '%' }}
></div>
<div className="text-sm text-gray-400 mt-1 flex justify-between">
<div>
<div data-testid="progress-bar-count">
{props.count} {props.suffix}
</div>
<div>
<div data-testid="progress-bar-total-count">
{props.totalCount} {props.suffix}
</div>
</div>

View File

@@ -6,6 +6,7 @@ export interface ComponentProps {
text: string;
color: Color;
style?: CSSProperties;
shouldAnimate: boolean;
}
const Statusbubble: FunctionComponent<ComponentProps> = (
@@ -14,7 +15,9 @@ const Statusbubble: FunctionComponent<ComponentProps> = (
return (
<div className="flex" style={props.style}>
<div
className="animate-pulse flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full mr-2"
className={`flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full mr-2 ${
props.shouldAnimate ? 'animate-pulse' : ''
}`}
style={{
backgroundColor: props.color
? props.color.toString()

View File

@@ -0,0 +1,425 @@
import React from 'react';
import { faker } from '@faker-js/faker';
import { render, fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import ComponentMetadata, {
ComponentCategory,
ComponentType,
} from 'Common/Types/Workflow/Component';
import IconProp from 'Common/Types/Icon/IconProp';
import ComponentsModal from '../../Components/Workflow/ComponentsModal';
/// @dev we use different UUID for (id & title), description, and category to ensure that the component is unique
const getComponentMetadata: Function = (
category?: string
): ComponentMetadata => {
const id: string = faker.datatype.uuid();
return {
id,
title: id,
description: faker.datatype.uuid(),
category: category || faker.datatype.uuid(),
iconProp: IconProp.Activity,
componentType: ComponentType.Component,
arguments: [],
returnValues: [],
inPorts: [],
outPorts: [],
};
};
const getComponentCategory: Function = (name?: string): ComponentCategory => {
return {
name: name || faker.datatype.uuid(),
description: `Description for ${name}`,
icon: IconProp.Activity,
};
};
describe('ComponentsModal', () => {
const mockedCategories: ComponentCategory[] = [
getComponentCategory(),
getComponentCategory(),
getComponentCategory(),
getComponentCategory(),
];
const mockedComponents: ComponentMetadata[] = [
getComponentMetadata(mockedCategories[0]?.name),
getComponentMetadata(mockedCategories[1]?.name),
getComponentMetadata(mockedCategories[2]?.name),
getComponentMetadata(mockedCategories[3]?.name),
];
const mockOnCloseModal: jest.Mock = jest.fn();
const mockOnComponentClick: jest.Mock = jest.fn();
it('should render without crashing', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
});
it('should display search input', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument();
});
it('should display categories and components', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
for (const cat of mockedCategories) {
expect(screen.getByText(cat.name)).toBeInTheDocument();
}
for (const comp of mockedComponents) {
expect(screen.getByText(comp.title)).toBeInTheDocument();
}
});
it('should call onCloseModal when the close button is clicked', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
fireEvent.click(screen.getByText('Close panel'));
expect(mockOnCloseModal).toHaveBeenCalled();
});
it('should call onComponentClick when a component is selected', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
for (const [idx, comp] of mockedComponents.entries()) {
// simulate selecting a component
fireEvent.click(screen.getByText(comp.title));
expect(screen.getByText('Create')).not.toBeDisabled();
// simulate submitting
fireEvent.click(screen.getByText('Create'));
// check if onComponentClick was called with the selected component's metadata
expect(mockOnComponentClick).toHaveBeenNthCalledWith(idx + 1, comp);
}
});
it('should display a message when no components are available', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={[]}
categories={mockedCategories}
/>
);
expect(
screen.getByText(
'No components that match your search. If you are looking for an integration that does not exist currently - you can use Custom Code or API component to build anything you like. If you are an enterprise customer, feel free to talk to us and we will build it for you.'
)
).toBeInTheDocument();
});
it('should not display categories when there are no categories', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={[]}
/>
);
mockedCategories.forEach((category: ComponentCategory) => {
expect(screen.queryByText(category.name)).not.toBeInTheDocument();
});
});
it('should display no components message when search yields no results', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
fireEvent.change(screen.getByPlaceholderText('Search...'), {
target: { value: 'Non-existent Ccmponent' },
});
expect(
screen.getByText(
'No components that match your search. If you are looking for an integration that does not exist currently - you can use Custom Code or API component to build anything you like. If you are an enterprise customer, feel free to talk to us and we will build it for you.'
)
).toBeInTheDocument();
});
it('should disable submit button prop when no component is selected', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
const submitButton: HTMLElement = screen.getByText('Create');
expect(submitButton).toBeDisabled();
});
it('should change submitButtonDisabled to false when a component is selected', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
for (const comp of mockedComponents) {
fireEvent.click(screen.getByText(comp.title));
const submitButton: HTMLElement = screen.getByText('Create');
expect(submitButton).not.toBeDisabled();
}
});
// search tests
it('should filter components based on search input', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
mockedComponents.forEach((comp: ComponentMetadata) => {
const partialTitle: string = comp.title.substring(
0,
comp.title.length - comp.title.length / 2
);
fireEvent.change(screen.getByPlaceholderText('Search...'), {
target: { value: partialTitle },
});
expect(screen.getByText(comp.title)).toBeInTheDocument();
// check other components are not displayed
mockedComponents
.filter((c: ComponentMetadata) => {
return c.title !== comp.title;
})
.forEach((c: ComponentMetadata) => {
return expect(
screen.queryByText(c.title)
).not.toBeInTheDocument();
});
});
});
it('should filter components based on description when searching', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
mockedComponents.forEach((comp: ComponentMetadata) => {
fireEvent.change(screen.getByPlaceholderText('Search...'), {
target: { value: comp.description },
});
expect(screen.getByText(comp.title)).toBeInTheDocument();
// check other components are not displayed
mockedComponents
.filter((c: ComponentMetadata) => {
return c.title !== comp.title;
})
.forEach((c: ComponentMetadata) => {
return expect(
screen.queryByText(c.title)
).not.toBeInTheDocument();
});
});
});
it('should filter components based on category when searching', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
mockedComponents.forEach((comp: ComponentMetadata) => {
fireEvent.change(screen.getByPlaceholderText('Search...'), {
target: { value: comp.category },
});
expect(screen.getByText(comp.title)).toBeInTheDocument();
// check other components are not displayed
mockedComponents
.filter((c: ComponentMetadata) => {
return c.category !== comp.category;
})
.forEach((c: ComponentMetadata) => {
return expect(
screen.queryByText(c.title)
).not.toBeInTheDocument();
});
});
});
it('should show all components when search is cleared', () => {
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
mockedComponents.forEach((comp: ComponentMetadata) => {
const searchInput: HTMLElement =
screen.getByPlaceholderText('Search...');
fireEvent.change(searchInput, { target: { value: comp.title } });
fireEvent.change(searchInput, { target: { value: '' } }); // clear search
mockedComponents.forEach((c: ComponentMetadata) => {
return expect(screen.getByText(c.title)).toBeInTheDocument();
});
});
});
it('should return multiple components when similar titles match', () => {
// we add a new component where its title is a substring of another component's title
const commonWord: string =
mockedComponents[0]?.title.substring(0, 5) || '';
const newComponent: ComponentMetadata = getComponentMetadata(
mockedCategories[1]?.name
);
newComponent.title += commonWord;
mockedComponents.push(newComponent);
const componentsWithCommonWord: ComponentMetadata[] =
mockedComponents.filter((comp: ComponentMetadata) => {
return comp.title.includes(commonWord);
});
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
fireEvent.change(screen.getByPlaceholderText('Search...'), {
target: { value: commonWord },
});
componentsWithCommonWord.forEach((comp: ComponentMetadata) => {
expect(screen.getByText(comp.title)).toBeInTheDocument();
});
});
it('should return return components with similar descriptions', () => {
// we add a new component where its title is a substring of another component's description
const partialDescription: string =
mockedComponents[0]?.description.substring(0, 10) || '';
const newComponent: ComponentMetadata = getComponentMetadata(
mockedCategories[1]?.name
);
newComponent.title = partialDescription || '';
mockedComponents.push(newComponent);
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
fireEvent.change(screen.getByPlaceholderText('Search...'), {
target: { value: partialDescription },
});
expect(
screen.getAllByText(new RegExp(partialDescription, 'i'))
).toHaveLength(2);
});
it('should return components with the same category', () => {
// we add two components with the same category as the first component
const commonCategory: string | undefined =
mockedComponents[0]?.category;
mockedComponents.push(getComponentMetadata(commonCategory));
mockedComponents.push(getComponentMetadata(commonCategory));
const componentsInCommonCategory: ComponentMetadata[] =
mockedComponents.filter((comp: ComponentMetadata) => {
return comp.category === commonCategory;
});
render(
<ComponentsModal
componentsType={ComponentType.Component}
onCloseModal={mockOnCloseModal}
onComponentClick={mockOnComponentClick}
components={mockedComponents}
categories={mockedCategories}
/>
);
fireEvent.change(screen.getByPlaceholderText('Search...'), {
target: { value: commonCategory },
});
componentsInCommonCategory.forEach((comp: ComponentMetadata) => {
expect(screen.getByText(comp.title)).toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,386 @@
import React from 'react';
import { act } from 'react-test-renderer';
import { faker } from '@faker-js/faker';
import {
render,
fireEvent,
screen,
waitFor,
queryByAttribute,
queryAllByAttribute,
queryByTestId,
} from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import MimeType from 'Common/Types/File/MimeType';
import FileModel from 'Model/Models/File';
import ModelAPI from '../../Utils/ModelAPI/ModelAPI';
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import ObjectID from 'Common/Types/ObjectID';
import FilePicker from '../../Components/FilePicker/FilePicker';
const mockOnChange: jest.Mock = jest.fn();
const mockOnBlur: jest.Mock = jest.fn();
jest.mock('../../Utils/ModelAPI/ModelAPI', () => {
return {
create: jest.fn(),
};
});
interface DefaultProps {
onBlur: () => void;
onChange: (files: FileModel[]) => void;
mimeTypes: MimeType[];
isOpen: boolean;
onClose: () => void;
initialValue?: FileModel | FileModel[];
value?: FileModel[] | undefined;
isMultiFilePicker?: boolean;
readOnly?: boolean;
}
interface DataTransfer {
dataTransfer: {
files: File[];
types: string[];
};
}
const mockCreateResponse: Function = async (
file: File
): Promise<HTTPResponse<FileModel>> => {
return new HTTPResponse(
200,
{
file: (await file.arrayBuffer()) as Buffer,
name: file.name,
type: file.type,
slug: file.name,
isPublic: true,
},
{}
);
};
const mockFileModel: Function = async (file: File): Promise<FileModel> => {
const fileModel: FileModel = new FileModel(new ObjectID('123'));
fileModel.name = file.name;
fileModel.type = file.type as MimeType;
fileModel.slug = file.name;
fileModel.isPublic = true;
fileModel.file = (await file.arrayBuffer()) as Buffer;
return fileModel;
};
const mockFile: Function = (): File => {
const mockArrayBuffer: jest.Mock = jest.fn();
mockArrayBuffer.mockResolvedValue(new ArrayBuffer(10)); // Mocked array buffer of size 10
const file: File = new File(
[faker.datatype.string()],
faker.system.commonFileName(MimeType.png),
{ type: MimeType.png }
);
file.arrayBuffer = mockArrayBuffer;
return file;
};
const defaultProps: DefaultProps = {
onBlur: mockOnBlur,
onChange: mockOnChange,
mimeTypes: [MimeType.png],
isOpen: true,
onClose: jest.fn(),
};
describe('FilePicker', () => {
const MOCK_FILE_URL: string = 'https://mock-file-url';
beforeAll(() => {
global.URL.createObjectURL = jest.fn(() => {
return MOCK_FILE_URL;
});
});
afterAll(() => {
(
global.URL.createObjectURL as jest.MockedFunction<
typeof global.URL.createObjectURL
>
).mockRestore();
});
beforeEach(() => {
delete defaultProps.isMultiFilePicker;
delete defaultProps.initialValue;
delete defaultProps.value;
delete defaultProps.readOnly;
});
it('should render without crashing', () => {
render(<FilePicker {...defaultProps} />);
expect(screen.getByText('Upload a file')).toBeInTheDocument();
expect(screen.getByRole('complementary')).toBeInTheDocument(); // aside element
});
it('should render with initial value', async () => {
defaultProps.initialValue = await mockFileModel(mockFile());
const { container } = render(<FilePicker {...defaultProps} />);
expect(
queryByAttribute('src', container, MOCK_FILE_URL)
).toBeInTheDocument();
});
it('should not render if file is missing the `file` attribute', async () => {
const file: FileModel = await mockFileModel(mockFile());
delete file.file;
defaultProps.initialValue = file;
const { container } = render(<FilePicker {...defaultProps} />);
expect(
queryByAttribute('src', container, MOCK_FILE_URL)
).not.toBeInTheDocument();
});
it('should render with initial value as array', async () => {
defaultProps.initialValue = [
await mockFileModel(mockFile()),
await mockFileModel(mockFile()),
];
render(<FilePicker {...defaultProps} />);
const { container } = render(<FilePicker {...defaultProps} />);
expect(
queryAllByAttribute('src', container, MOCK_FILE_URL)
).toHaveLength(2);
});
it('should render with value array with one element', async () => {
defaultProps.value = [await mockFileModel(mockFile())];
const { container } = render(<FilePicker {...defaultProps} />);
expect(
queryByAttribute('src', container, MOCK_FILE_URL)
).toBeInTheDocument();
});
it('should render with value array with more than one element', async () => {
defaultProps.value = [
await mockFileModel(mockFile()),
await mockFileModel(mockFile()),
];
render(<FilePicker {...defaultProps} />);
const { container } = render(<FilePicker {...defaultProps} />);
expect(
queryAllByAttribute('src', container, MOCK_FILE_URL)
).toHaveLength(2);
});
it('should not upload file when dropped and readOnly is true', async () => {
defaultProps.readOnly = true;
const file: File = mockFile();
const data: DataTransfer = {
dataTransfer: {
files: [file],
types: ['Files'],
},
};
const createResponse: HTTPResponse<FileModel> =
await mockCreateResponse(file);
(
ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
).mockResolvedValue(createResponse);
const { container } = render(<FilePicker {...defaultProps} />);
const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
fireEvent.drop(dropzone, data);
await waitFor(() => {
expect(
queryByAttribute('src', container, MOCK_FILE_URL)
).not.toBeInTheDocument();
});
});
it('should throw an "File too large" when uploading a file that fails on arrayBuffer()', async () => {
const file: File = mockFile();
file.arrayBuffer = jest
.fn()
.mockRejectedValue(new Error('File too large'));
const data: DataTransfer = {
dataTransfer: {
files: [file],
types: ['Files'],
},
};
const { container } = render(<FilePicker {...defaultProps} />);
const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
fireEvent.drop(dropzone, data);
await waitFor(() => {
expect(
queryByAttribute('src', container, MOCK_FILE_URL)
).not.toBeInTheDocument();
});
});
it('should upload a file when dropped', async () => {
const file: File = mockFile();
const data: DataTransfer = {
dataTransfer: {
files: [file],
types: ['Files'],
},
};
const createResponse: HTTPResponse<FileModel> =
await mockCreateResponse(file);
(
ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
).mockResolvedValue(createResponse);
const { container } = render(<FilePicker {...defaultProps} />);
const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
await act(async () => {
fireEvent.drop(dropzone, data);
});
await waitFor(() => {
expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
expect(mockOnBlur).toHaveBeenCalled();
expect(mockOnBlur).toHaveBeenCalled();
expect(
queryByAttribute('src', container, MOCK_FILE_URL)
).toBeInTheDocument();
});
});
it('should upload a file when dropped', async () => {
const file: File = mockFile();
const data: DataTransfer = {
dataTransfer: {
files: [file],
types: ['Files'],
},
};
const createResponse: HTTPResponse<FileModel> =
await mockCreateResponse(file);
(
ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
).mockResolvedValue(createResponse);
const { container } = render(<FilePicker {...defaultProps} />);
const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
await act(async () => {
fireEvent.drop(dropzone, data);
});
await waitFor(() => {
expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
expect(mockOnBlur).toHaveBeenCalled();
expect(mockOnBlur).toHaveBeenCalled();
expect(
queryByAttribute('src', container, MOCK_FILE_URL)
).toBeInTheDocument();
});
});
it('should show loader a file when files are being uploaded', async () => {
const uploadPromise: Promise<unknown> = new Promise((resolve: any) => {
(global as any).mockUploadResolve = resolve; // Store resolve function globally or in a scope accessible outside the test
});
(
ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
).mockImplementation((): any => {
return uploadPromise;
});
const file: File = mockFile();
const data: DataTransfer = {
dataTransfer: {
files: [file],
types: ['Files'],
},
};
const createResponse: HTTPResponse<FileModel> =
await mockCreateResponse(file);
const { container } = render(<FilePicker {...defaultProps} />);
const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
await act(async () => {
fireEvent.drop(dropzone, data);
});
expect(queryByTestId(container, 'loader')).toBeInTheDocument();
(global as any).mockUploadResolve(createResponse);
await waitFor(() => {
expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
expect(mockOnBlur).toHaveBeenCalled();
expect(mockOnBlur).toHaveBeenCalled();
expect(
queryByAttribute('src', container, MOCK_FILE_URL)
).toBeInTheDocument();
});
});
it('should delete an uploaded file when clicking on it', async () => {
const file: File = mockFile();
const data: DataTransfer = {
dataTransfer: {
files: [file],
types: ['Files'],
},
};
const createResponse: HTTPResponse<FileModel> =
await mockCreateResponse(file);
(
ModelAPI.create as jest.MockedFunction<typeof ModelAPI.create>
).mockResolvedValue(createResponse);
const { container } = render(<FilePicker {...defaultProps} />);
const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
await act(async () => {
fireEvent.drop(dropzone, data);
});
await waitFor(() => {
// file should be in the dropzone
expect(
queryByAttribute('src', container, MOCK_FILE_URL)
).toBeInTheDocument();
});
const deleteIcon: ChildNode = screen
.getByRole('icon')
.childNodes.item(0); // svg item
// remove file by clicking on it
if (deleteIcon) {
await act(async () => {
fireEvent.click(deleteIcon.childNodes.item(0), data);
});
}
await waitFor(() => {
// file should have been removed
expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
expect(
queryByAttribute('src', container, MOCK_FILE_URL)
).not.toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,65 @@
import * as React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import ProbeElement from '../../Components/Probe/Probe';
import Probe from 'Model/Models/Probe';
import ObjectID from 'Common/Types/ObjectID';
describe('ProbeElement Component', () => {
const mockProbe: Probe = new Probe();
mockProbe.name = 'Test Probe';
mockProbe.iconFileId = new ObjectID('12345');
test('should display the probe name', () => {
render(<ProbeElement probe={mockProbe} />);
const probeName: HTMLElement = screen.getByTestId('probe-name');
expect(probeName).toBeInTheDocument();
});
test('should display the image when iconFileId is present', () => {
render(<ProbeElement probe={mockProbe} />);
const imageElement: HTMLImageElement =
screen.getByTestId('probe-image');
expect(imageElement).toBeInTheDocument();
expect(imageElement).toHaveAttribute('alt', 'Test Probe');
});
test('should display the default icon when iconFileId is not present', () => {
const probeWithoutIcon: Probe = new Probe();
probeWithoutIcon.name = 'Test Probe';
render(<ProbeElement probe={probeWithoutIcon} />);
const iconElement: HTMLElement = screen.getByTestId('probe-icon');
expect(iconElement).toBeInTheDocument();
});
test('should display "No probe found" when no probe is provided', () => {
render(<ProbeElement probe={null} />);
const noProbeText: HTMLElement = screen.getByTestId('probe-not-found');
expect(noProbeText).toBeInTheDocument();
});
test('should display the image with correct src when iconFileId is present', () => {
const probeWithIcon: Probe = new Probe();
probeWithIcon.iconFileId = new ObjectID('icon123');
probeWithIcon.name = 'Probe with Icon';
render(<ProbeElement probe={probeWithIcon} />);
const imageElement: HTMLImageElement =
screen.getByTestId('probe-image');
expect(imageElement).toBeInTheDocument();
expect(imageElement).toHaveAttribute(
'src',
expect.stringContaining('icon123')
);
});
test('should display the default icon when iconFileId is not present', () => {
const probeWithoutIcon: Probe = new Probe();
probeWithoutIcon.name = 'Probe without Icon';
render(<ProbeElement probe={probeWithoutIcon} />);
const iconElement: HTMLElement = screen.getByTestId('probe-icon');
expect(iconElement).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,51 @@
import * as React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import ProgressBar from '../../Components/ProgressBar/ProgressBar';
describe('ProgressBar Component', () => {
function getProgressBar(): HTMLElement {
const element: HTMLElement = screen.getByTestId('progress-bar');
if (!element) {
throw 'Not Found';
}
return element;
}
test('should calculate and display the correct percentage', () => {
render(<ProgressBar count={0} totalCount={100} suffix="items" />);
const progressBar: HTMLElement = getProgressBar();
expect(progressBar).toHaveStyle({ width: '0%' });
});
test('should display the correct count and total count with suffix', () => {
render(<ProgressBar count={30} totalCount={99} suffix="items" />);
const countText: HTMLElement = screen.getByTestId('progress-bar-count');
const totalCountText: HTMLElement = screen.getByTestId(
'progress-bar-total-count'
);
expect(countText).toBeInTheDocument();
expect(totalCountText).toBeInTheDocument();
expect(countText.innerHTML).toEqual('30 items');
expect(totalCountText.innerHTML).toEqual('99 items');
});
test('should handle zero total count without crashing', () => {
render(<ProgressBar count={30} totalCount={0} suffix="items" />);
const progressBar: HTMLElement = getProgressBar();
expect(progressBar).toHaveStyle({ width: '100%' });
});
test('should round up the percentage to the nearest integer', () => {
render(<ProgressBar count={33} totalCount={100} suffix="items" />);
const progressBar: HTMLElement = getProgressBar();
expect(progressBar).toHaveStyle({ width: '33%' });
});
test('should cap the percentage at 100 if count exceeds total count', () => {
render(<ProgressBar count={150} totalCount={100} suffix="items" />);
const progressBar: HTMLElement = getProgressBar();
expect(progressBar).toHaveStyle({ width: '100%' });
});
});

View File

@@ -175,7 +175,8 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
getAllEnvVars()
) &&
props.paymentMethodsCount !== undefined &&
props.paymentMethodsCount === 0 ? (
props.paymentMethodsCount === 0 &&
!props.selectedProject.resellerId ? (
<Button
title="Add Card Details"
onClick={() => {

View File

@@ -90,6 +90,7 @@ const MonitorCriteriaInstanceElement: FunctionComponent<ComponentProps> = (
}
)?.color as Color) || Black
}
shouldAnimate={false}
text={
(props.monitorStatusOptions.find(
(option: IncidentSeverity) => {

View File

@@ -173,6 +173,7 @@ const MonitorStepsElement: FunctionComponent<ComponentProps> = (
}
)?.color as Color) || Black
}
shouldAnimate={false}
text={
(monitorStatusOptions.find(
(option: IncidentSeverity) => {
@@ -191,6 +192,7 @@ const MonitorStepsElement: FunctionComponent<ComponentProps> = (
<Statusbubble
color={defaultMonitorStatus.color!}
text={defaultMonitorStatus.name!}
shouldAnimate={false}
/>
)}
</div>

View File

@@ -214,7 +214,11 @@ const MonitorsTable: FunctionComponent<ComponentProps> = (
if (item && item['disableActiveMonitoring']) {
return (
<Statusbubble color={Grey} text={'Disabled'} />
<Statusbubble
shouldAnimate={false}
color={Grey}
text={'Disabled'}
/>
);
}
@@ -227,6 +231,7 @@ const MonitorsTable: FunctionComponent<ComponentProps> = (
] as JSONObject
)['color'] as Color
}
shouldAnimate={true}
text={
(
item[

View File

@@ -67,6 +67,7 @@ const CurrentStatusElement: FunctionComponent<ComponentProps> = (
<Statusbubble
color={currentGroupStatus.color! as Color}
text={currentGroupStatus.name! as string}
shouldAnimate={true}
/>
);
};

View File

@@ -10,6 +10,7 @@ import Incident from 'Model/Models/Incident';
import Project from 'Model/Models/Project';
import Monitor from 'Model/Models/Monitor';
import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance';
import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection';
export interface ComponentProps {
project?: Project | undefined;
@@ -20,61 +21,70 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
): ReactElement => {
return (
<SideMenu>
<SideMenuItem<Incident>
link={{
title: 'Active Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route
),
}}
icon={IconProp.Alert}
badgeType={BadgeType.DANGER}
modelType={Incident}
countQuery={{
projectId: props.project?._id,
currentIncidentState: {
isResolvedState: false,
},
}}
/>
<SideMenuSection title="Incidents">
<SideMenuItem<Incident>
link={{
title: 'Active',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route
),
}}
icon={IconProp.Alert}
badgeType={BadgeType.DANGER}
modelType={Incident}
countQuery={{
projectId: props.project?._id,
currentIncidentState: {
isResolvedState: false,
},
}}
/>
</SideMenuSection>
<SideMenuItem<Monitor>
link={{
title: 'Inoperational Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME_NOT_OPERATIONAL_MONITORS] as Route
),
}}
icon={IconProp.AltGlobe}
countQuery={{
projectId: props.project?._id,
currentMonitorStatus: {
isOperationalState: false,
},
}}
modelType={Monitor}
badgeType={BadgeType.DANGER}
/>
<SideMenuSection title="Monitors">
<SideMenuItem<Monitor>
link={{
title: 'Inoperational',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.HOME_NOT_OPERATIONAL_MONITORS
] as Route
),
}}
icon={IconProp.AltGlobe}
countQuery={{
projectId: props.project?._id,
currentMonitorStatus: {
isOperationalState: false,
},
}}
modelType={Monitor}
badgeType={BadgeType.DANGER}
/>
</SideMenuSection>
<SideMenuItem<ScheduledMaintenance>
link={{
title: 'Ongoing Events',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS
] as Route
),
}}
icon={IconProp.Clock}
countQuery={{
projectId: props.project?._id,
currentScheduledMaintenanceState: {
isOngoingState: true,
},
}}
modelType={ScheduledMaintenance}
badgeType={BadgeType.WARNING}
/>
<SideMenuSection title="Scheduled Events">
<SideMenuItem<ScheduledMaintenance>
link={{
title: 'Ongoing',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap
.HOME_ONGOING_SCHEDULED_MAINTENANCE_EVENTS
] as Route
),
}}
icon={IconProp.Clock}
countQuery={{
projectId: props.project?._id,
currentScheduledMaintenanceState: {
isOngoingState: true,
},
}}
modelType={ScheduledMaintenance}
badgeType={BadgeType.WARNING}
/>
</SideMenuSection>
</SideMenu>
);
};

View File

@@ -304,6 +304,7 @@ const MonitorView: FunctionComponent<PageComponentProps> = (
<Statusbubble
color={Grey}
text={'Disabled'}
shouldAnimate={false}
/>
);
}
@@ -317,6 +318,7 @@ const MonitorView: FunctionComponent<PageComponentProps> = (
] as JSONObject
)['color'] as Color
}
shouldAnimate={true}
text={
(
item[

View File

@@ -193,6 +193,7 @@ const StatusTimeline: FunctionComponent<PageComponentProps> = (
'color'
] as Color
}
shouldAnimate={false}
text={
(item['monitorStatus'] as JSONObject)[
'name'

View File

@@ -87,6 +87,7 @@ const MonitorGroupView: FunctionComponent<PageComponentProps> = (
<Statusbubble
text={currentGroupStatus?.name || 'Operational'}
color={currentGroupStatus?.color || Green}
shouldAnimate={true}
/>
);
};

View File

@@ -263,6 +263,7 @@ const MonitorGroupResources: FunctionComponent<PageComponentProps> = (
<Statusbubble
color={monitorStatus.color! as Color}
text={monitorStatus.name! as string}
shouldAnimate={true}
/>
);
},

View File

@@ -153,6 +153,7 @@ const Monitors: FunctionComponent<PageComponentProps> = (
<StatusBubble
color={item['color'] as Color}
text={item['name'] as string}
shouldAnimate={false}
/>
);
},

View File

@@ -117,6 +117,7 @@ const ProbePage: FunctionComponent<PageComponentProps> = (
<Statusbubble
text={'Connected'}
color={Green}
shouldAnimate={true}
/>
);
}
@@ -125,6 +126,7 @@ const ProbePage: FunctionComponent<PageComponentProps> = (
<Statusbubble
text={'Disconnected'}
color={Red}
shouldAnimate={false}
/>
);
},
@@ -279,6 +281,7 @@ const ProbePage: FunctionComponent<PageComponentProps> = (
<Statusbubble
text={'Connected'}
color={Green}
shouldAnimate={true}
/>
);
}
@@ -287,6 +290,7 @@ const ProbePage: FunctionComponent<PageComponentProps> = (
<Statusbubble
text={'Disconnected'}
color={Red}
shouldAnimate={false}
/>
);
},

View File

@@ -1,4 +1,7 @@
# IP Address of Probes in OneUptime.com
US: 51.159.99.250
EU: 62.210.173.51
These IP's can change, we will let you know in advance if this happens.

View File

@@ -25,6 +25,7 @@ import Team from './Team';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerTeam,
],
read: [
@@ -36,11 +37,13 @@ import Team from './Team';
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanDeleteIncidentOwnerTeam,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditIncidentOwnerTeam,
],
})
@@ -66,6 +69,7 @@ export default class IncidentOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerTeam,
],
read: [
@@ -102,6 +106,7 @@ export default class IncidentOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerTeam,
],
read: [
@@ -132,6 +137,7 @@ export default class IncidentOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerTeam,
],
read: [
@@ -168,6 +174,7 @@ export default class IncidentOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerTeam,
],
read: [
@@ -197,6 +204,7 @@ export default class IncidentOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerTeam,
],
read: [
@@ -233,6 +241,7 @@ export default class IncidentOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerTeam,
],
read: [
@@ -263,6 +272,7 @@ export default class IncidentOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerTeam,
],
read: [
@@ -299,6 +309,7 @@ export default class IncidentOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerTeam,
],
read: [
@@ -381,6 +392,7 @@ export default class IncidentOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerTeam,
],
read: [

View File

@@ -24,6 +24,7 @@ import Incident from './Incident';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerUser,
],
read: [
@@ -35,11 +36,13 @@ import Incident from './Incident';
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanDeleteIncidentOwnerUser,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditIncidentOwnerUser,
],
})
@@ -65,6 +68,7 @@ export default class IncidentOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerUser,
],
read: [
@@ -101,6 +105,7 @@ export default class IncidentOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerUser,
],
read: [
@@ -131,6 +136,7 @@ export default class IncidentOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerUser,
],
read: [
@@ -167,6 +173,7 @@ export default class IncidentOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerUser,
],
read: [
@@ -196,6 +203,7 @@ export default class IncidentOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerUser,
],
read: [
@@ -232,6 +240,7 @@ export default class IncidentOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerUser,
],
read: [
@@ -262,6 +271,7 @@ export default class IncidentOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerUser,
],
read: [
@@ -298,6 +308,7 @@ export default class IncidentOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerUser,
],
read: [
@@ -380,6 +391,7 @@ export default class IncidentOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentOwnerUser,
],
read: [

View File

@@ -25,6 +25,7 @@ import Team from './Team';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerTeam,
],
read: [
@@ -36,11 +37,13 @@ import Team from './Team';
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanDeleteIncidentTemplateOwnerTeam,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditIncidentTemplateOwnerTeam,
],
})
@@ -66,6 +69,7 @@ export default class IncidentTemplateOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerTeam,
],
read: [
@@ -102,6 +106,7 @@ export default class IncidentTemplateOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerTeam,
],
read: [
@@ -132,6 +137,7 @@ export default class IncidentTemplateOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerTeam,
],
read: [
@@ -168,6 +174,7 @@ export default class IncidentTemplateOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerTeam,
],
read: [
@@ -197,6 +204,7 @@ export default class IncidentTemplateOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerTeam,
],
read: [
@@ -233,6 +241,7 @@ export default class IncidentTemplateOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerTeam,
],
read: [
@@ -263,6 +272,7 @@ export default class IncidentTemplateOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerTeam,
],
read: [
@@ -299,6 +309,7 @@ export default class IncidentTemplateOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerTeam,
],
read: [
@@ -381,6 +392,7 @@ export default class IncidentTemplateOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerTeam,
],
read: [

View File

@@ -24,22 +24,26 @@ import IncidentTemplate from './IncidentTemplate';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerUser,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanDeleteIncidentTemplateOwnerUser,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditIncidentTemplateOwnerUser,
],
})
@@ -65,12 +69,14 @@ export default class IncidentTemplateOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerUser,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
update: [],
@@ -101,12 +107,14 @@ export default class IncidentTemplateOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerUser,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
update: [],
@@ -131,12 +139,14 @@ export default class IncidentTemplateOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerUser,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
update: [],
@@ -167,12 +177,14 @@ export default class IncidentTemplateOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerUser,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
update: [],
@@ -196,12 +208,14 @@ export default class IncidentTemplateOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerUser,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
update: [],
@@ -232,12 +246,14 @@ export default class IncidentTemplateOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerUser,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
update: [],
@@ -262,12 +278,14 @@ export default class IncidentTemplateOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerUser,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
update: [],
@@ -298,12 +316,14 @@ export default class IncidentTemplateOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerUser,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
update: [],
@@ -327,6 +347,7 @@ export default class IncidentTemplateOwnerUser extends AccessControlModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
update: [],
@@ -359,6 +380,7 @@ export default class IncidentTemplateOwnerUser extends AccessControlModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
update: [],
@@ -380,12 +402,14 @@ export default class IncidentTemplateOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateIncidentTemplateOwnerUser,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ProjectMember,
Permission.CanReadIncidentTemplateOwnerUser,
],
update: [],

View File

@@ -35,6 +35,7 @@ import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateProjectLabel,
],
read: [
@@ -46,11 +47,13 @@ import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan';
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanDeleteProjectLabel,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditProjectLabel,
],
})
@@ -78,6 +81,7 @@ export default class Label extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateProjectLabel,
],
read: [
@@ -114,6 +118,7 @@ export default class Label extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateProjectLabel,
],
read: [
@@ -144,6 +149,7 @@ export default class Label extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateProjectLabel,
],
read: [
@@ -155,6 +161,7 @@ export default class Label extends AccessControlModel {
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditProjectLabel,
],
})
@@ -201,6 +208,7 @@ export default class Label extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateProjectLabel,
],
read: [
@@ -212,6 +220,7 @@ export default class Label extends AccessControlModel {
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditProjectLabel,
],
})
@@ -232,6 +241,7 @@ export default class Label extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateProjectLabel,
],
read: [
@@ -268,6 +278,7 @@ export default class Label extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateProjectLabel,
],
read: [
@@ -350,6 +361,7 @@ export default class Label extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateProjectLabel,
],
read: [
@@ -361,6 +373,7 @@ export default class Label extends AccessControlModel {
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditProjectLabel,
],
})

View File

@@ -25,6 +25,7 @@ import Team from './Team';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerTeam,
],
read: [
@@ -36,11 +37,13 @@ import Team from './Team';
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanDeleteMonitorGroupOwnerTeam,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditMonitorGroupOwnerTeam,
],
})
@@ -66,6 +69,7 @@ export default class MonitorGroupOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerTeam,
],
read: [
@@ -102,6 +106,7 @@ export default class MonitorGroupOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerTeam,
],
read: [
@@ -132,6 +137,7 @@ export default class MonitorGroupOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerTeam,
],
read: [
@@ -168,6 +174,7 @@ export default class MonitorGroupOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerTeam,
],
read: [
@@ -197,6 +204,7 @@ export default class MonitorGroupOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerTeam,
],
read: [
@@ -233,6 +241,7 @@ export default class MonitorGroupOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerTeam,
],
read: [
@@ -263,6 +272,7 @@ export default class MonitorGroupOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerTeam,
],
read: [
@@ -299,6 +309,7 @@ export default class MonitorGroupOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerTeam,
],
read: [
@@ -381,6 +392,7 @@ export default class MonitorGroupOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerTeam,
],
read: [

View File

@@ -24,6 +24,7 @@ import MonitorGroup from './MonitorGroup';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerUser,
],
read: [
@@ -35,11 +36,13 @@ import MonitorGroup from './MonitorGroup';
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanDeleteMonitorGroupOwnerUser,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditMonitorGroupOwnerUser,
],
})
@@ -65,6 +68,7 @@ export default class MonitorGroupOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerUser,
],
read: [
@@ -101,6 +105,7 @@ export default class MonitorGroupOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerUser,
],
read: [
@@ -131,6 +136,7 @@ export default class MonitorGroupOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerUser,
],
read: [
@@ -167,6 +173,7 @@ export default class MonitorGroupOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerUser,
],
read: [
@@ -196,6 +203,7 @@ export default class MonitorGroupOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerUser,
],
read: [
@@ -232,6 +240,7 @@ export default class MonitorGroupOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerUser,
],
read: [
@@ -262,6 +271,7 @@ export default class MonitorGroupOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerUser,
],
read: [
@@ -298,6 +308,7 @@ export default class MonitorGroupOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerUser,
],
read: [
@@ -380,6 +391,7 @@ export default class MonitorGroupOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorGroupOwnerUser,
],
read: [

View File

@@ -25,6 +25,7 @@ import Team from './Team';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerTeam,
],
read: [
@@ -36,11 +37,13 @@ import Team from './Team';
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanDeleteMonitorOwnerTeam,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditMonitorOwnerTeam,
],
})
@@ -66,6 +69,7 @@ export default class MonitorOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerTeam,
],
read: [
@@ -102,6 +106,7 @@ export default class MonitorOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerTeam,
],
read: [
@@ -132,6 +137,7 @@ export default class MonitorOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerTeam,
],
read: [
@@ -168,6 +174,7 @@ export default class MonitorOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerTeam,
],
read: [
@@ -197,6 +204,7 @@ export default class MonitorOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerTeam,
],
read: [
@@ -233,6 +241,7 @@ export default class MonitorOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerTeam,
],
read: [
@@ -263,6 +272,7 @@ export default class MonitorOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerTeam,
],
read: [
@@ -299,6 +309,7 @@ export default class MonitorOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerTeam,
],
read: [
@@ -381,6 +392,7 @@ export default class MonitorOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerTeam,
],
read: [

View File

@@ -24,6 +24,7 @@ import Monitor from './Monitor';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerUser,
],
read: [
@@ -35,11 +36,13 @@ import Monitor from './Monitor';
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanDeleteMonitorOwnerUser,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditMonitorOwnerUser,
],
})
@@ -65,6 +68,7 @@ export default class MonitorOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerUser,
],
read: [
@@ -101,6 +105,7 @@ export default class MonitorOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerUser,
],
read: [
@@ -131,6 +136,7 @@ export default class MonitorOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerUser,
],
read: [
@@ -167,6 +173,7 @@ export default class MonitorOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerUser,
],
read: [
@@ -196,6 +203,7 @@ export default class MonitorOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerUser,
],
read: [
@@ -232,6 +240,7 @@ export default class MonitorOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerUser,
],
read: [
@@ -262,6 +271,7 @@ export default class MonitorOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerUser,
],
read: [
@@ -298,6 +308,7 @@ export default class MonitorOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerUser,
],
read: [
@@ -380,6 +391,7 @@ export default class MonitorOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateMonitorOwnerUser,
],
read: [

View File

@@ -40,6 +40,7 @@ import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan';
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
delete: [
@@ -75,6 +76,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [],
@@ -110,6 +112,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [],
@@ -139,6 +142,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [
@@ -165,6 +169,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [],
@@ -192,6 +197,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [
@@ -222,6 +228,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [],
@@ -257,6 +264,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [],
@@ -279,6 +287,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [],
@@ -310,6 +319,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [],
@@ -338,6 +348,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [
@@ -388,6 +399,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [
@@ -414,6 +426,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [
@@ -439,6 +452,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [
@@ -465,6 +479,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [
@@ -490,6 +505,7 @@ export default class ProjectSmtpConfig extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSMTPConfig,
],
update: [

View File

@@ -48,9 +48,7 @@ import Team from './Team';
Permission.ProjectOwner,
Permission.ProjectUser,
Permission.UnAuthorizedSsoUser,
Permission.ProjectAdmin,
Permission.ProjectUser,
Permission.UnAuthorizedSsoUser,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
],
delete: [
@@ -88,8 +86,7 @@ export default class ProjectSSO extends BaseModel {
Permission.ProjectUser,
Permission.Public,
Permission.UnAuthorizedSsoUser,
Permission.ProjectUser,
Permission.UnAuthorizedSsoUser,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
],
update: [],
@@ -128,6 +125,7 @@ export default class ProjectSSO extends BaseModel {
Permission.ProjectUser,
Permission.Public,
Permission.UnAuthorizedSsoUser,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
Permission.ProjectUser,
Permission.UnAuthorizedSsoUser,
@@ -162,6 +160,7 @@ export default class ProjectSSO extends BaseModel {
Permission.ProjectUser,
Permission.Public,
Permission.UnAuthorizedSsoUser,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
Permission.ProjectUser,
Permission.UnAuthorizedSsoUser,
@@ -199,6 +198,7 @@ export default class ProjectSSO extends BaseModel {
Permission.ProjectUser,
Permission.Public,
Permission.UnAuthorizedSsoUser,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
],
update: [
@@ -227,7 +227,6 @@ export default class ProjectSSO extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanReadProjectSSO,
],
update: [
@@ -257,7 +256,6 @@ export default class ProjectSSO extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanReadProjectSSO,
],
update: [
@@ -287,6 +285,7 @@ export default class ProjectSSO extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
Permission.Public,
Permission.ProjectUser,
@@ -320,6 +319,7 @@ export default class ProjectSSO extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
],
update: [
@@ -391,7 +391,6 @@ export default class ProjectSSO extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanReadProjectSSO,
],
update: [
@@ -421,6 +420,7 @@ export default class ProjectSSO extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
],
update: [],
@@ -457,6 +457,7 @@ export default class ProjectSSO extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
],
update: [],
@@ -480,6 +481,7 @@ export default class ProjectSSO extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
],
update: [],
@@ -512,6 +514,7 @@ export default class ProjectSSO extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
],
update: [],
@@ -540,6 +543,7 @@ export default class ProjectSSO extends BaseModel {
Permission.ProjectAdmin,
Permission.ProjectUser,
Permission.UnAuthorizedSsoUser,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
],
update: [
@@ -566,6 +570,7 @@ export default class ProjectSSO extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectSSO,
],
update: [],

View File

@@ -25,6 +25,7 @@ import Team from './Team';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerTeam,
],
read: [
@@ -36,11 +37,13 @@ import Team from './Team';
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanDeleteScheduledMaintenanceOwnerTeam,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditScheduledMaintenanceOwnerTeam,
],
})
@@ -67,6 +70,7 @@ export default class ScheduledMaintenanceOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerTeam,
],
read: [
@@ -103,6 +107,7 @@ export default class ScheduledMaintenanceOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerTeam,
],
read: [
@@ -133,6 +138,7 @@ export default class ScheduledMaintenanceOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerTeam,
],
read: [
@@ -169,6 +175,7 @@ export default class ScheduledMaintenanceOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerTeam,
],
read: [
@@ -198,6 +205,7 @@ export default class ScheduledMaintenanceOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerTeam,
],
read: [
@@ -234,6 +242,7 @@ export default class ScheduledMaintenanceOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerTeam,
],
read: [
@@ -264,6 +273,7 @@ export default class ScheduledMaintenanceOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerTeam,
],
read: [
@@ -300,6 +310,7 @@ export default class ScheduledMaintenanceOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerTeam,
],
read: [
@@ -382,6 +393,7 @@ export default class ScheduledMaintenanceOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerTeam,
],
read: [

View File

@@ -24,6 +24,7 @@ import ScheduledMaintenance from './ScheduledMaintenance';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerUser,
],
read: [
@@ -35,11 +36,13 @@ import ScheduledMaintenance from './ScheduledMaintenance';
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanDeleteScheduledMaintenanceOwnerUser,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanEditScheduledMaintenanceOwnerUser,
],
})
@@ -66,6 +69,7 @@ export default class ScheduledMaintenanceOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerUser,
],
read: [
@@ -102,6 +106,7 @@ export default class ScheduledMaintenanceOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerUser,
],
read: [
@@ -132,6 +137,7 @@ export default class ScheduledMaintenanceOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerUser,
],
read: [
@@ -168,6 +174,7 @@ export default class ScheduledMaintenanceOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerUser,
],
read: [
@@ -197,6 +204,7 @@ export default class ScheduledMaintenanceOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerUser,
],
read: [
@@ -233,6 +241,7 @@ export default class ScheduledMaintenanceOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerUser,
],
read: [
@@ -263,6 +272,7 @@ export default class ScheduledMaintenanceOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerUser,
],
read: [
@@ -299,6 +309,7 @@ export default class ScheduledMaintenanceOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerUser,
],
read: [
@@ -381,6 +392,7 @@ export default class ScheduledMaintenanceOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateScheduledMaintenanceOwnerUser,
],
read: [

View File

@@ -28,10 +28,18 @@ import EnableWorkflow from 'Common/Types/Database/EnableWorkflow';
import IconProp from 'Common/Types/Icon/IconProp';
import StatusPage from './StatusPage';
import EnableDocumentation from 'Common/Types/Database/EnableDocumentation';
import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl';
import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan';
@EnableDocumentation()
@TenantColumn('projectId')
@CanAccessIfCanReadOn('statusPages')
@TableBillingAccessControl({
create: PlanSelect.Growth,
read: PlanSelect.Free,
update: PlanSelect.Growth,
delete: PlanSelect.Growth,
})
@TableAccessControl({
create: [
Permission.ProjectOwner,

View File

@@ -21,8 +21,16 @@ import IconProp from 'Common/Types/Icon/IconProp';
import StatusPage from './StatusPage';
import CanAccessIfCanReadOn from 'Common/Types/Database/CanAccessIfCanReadOn';
import EnableDocumentation from 'Common/Types/Database/EnableDocumentation';
import TableBillingAccessControl from 'Common/Types/Database/AccessControl/TableBillingAccessControl';
import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan';
@EnableDocumentation()
@TableBillingAccessControl({
create: PlanSelect.Growth,
read: PlanSelect.Free,
update: PlanSelect.Growth,
delete: PlanSelect.Growth,
})
@CanAccessIfCanReadOn('statusPage')
@TenantColumn('projectId')
@TableAccessControl({

View File

@@ -25,6 +25,7 @@ import Team from './Team';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerTeam,
],
read: [
@@ -66,6 +67,7 @@ export default class StatusPageOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerTeam,
],
read: [
@@ -102,6 +104,7 @@ export default class StatusPageOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerTeam,
],
read: [
@@ -132,6 +135,7 @@ export default class StatusPageOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerTeam,
],
read: [
@@ -168,6 +172,7 @@ export default class StatusPageOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerTeam,
],
read: [
@@ -197,6 +202,7 @@ export default class StatusPageOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerTeam,
],
read: [
@@ -233,6 +239,7 @@ export default class StatusPageOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerTeam,
],
read: [
@@ -263,6 +270,7 @@ export default class StatusPageOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerTeam,
],
read: [
@@ -299,6 +307,7 @@ export default class StatusPageOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerTeam,
],
read: [
@@ -381,6 +390,7 @@ export default class StatusPageOwnerTeam extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerTeam,
],
read: [

View File

@@ -24,6 +24,7 @@ import StatusPage from './StatusPage';
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerUser,
],
read: [
@@ -65,6 +66,7 @@ export default class StatusPageOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerUser,
],
read: [
@@ -101,6 +103,7 @@ export default class StatusPageOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerUser,
],
read: [
@@ -131,6 +134,7 @@ export default class StatusPageOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerUser,
],
read: [
@@ -167,6 +171,7 @@ export default class StatusPageOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerUser,
],
read: [
@@ -196,6 +201,7 @@ export default class StatusPageOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerUser,
],
read: [
@@ -232,6 +238,7 @@ export default class StatusPageOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerUser,
],
read: [
@@ -262,6 +269,7 @@ export default class StatusPageOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerUser,
],
read: [
@@ -298,6 +306,7 @@ export default class StatusPageOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerUser,
],
read: [
@@ -380,6 +389,7 @@ export default class StatusPageOwnerUser extends AccessControlModel {
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanCreateStatusPageOwnerUser,
],
read: [

View File

@@ -47,6 +47,7 @@ import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan';
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectTeam,
],
delete: [
@@ -92,6 +93,7 @@ export default class TeamPermission extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectTeam,
],
update: [],
@@ -128,6 +130,7 @@ export default class TeamPermission extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectTeam,
],
update: [],
@@ -158,6 +161,7 @@ export default class TeamPermission extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectTeam,
],
update: [],
@@ -193,6 +197,7 @@ export default class TeamPermission extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectTeam,
],
update: [],
@@ -219,6 +224,7 @@ export default class TeamPermission extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectTeam,
],
update: [],
@@ -254,6 +260,7 @@ export default class TeamPermission extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectTeam,
],
update: [],
@@ -280,6 +287,7 @@ export default class TeamPermission extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectTeam,
],
update: [],
@@ -334,6 +342,7 @@ export default class TeamPermission extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectTeam,
],
update: [
@@ -368,6 +377,7 @@ export default class TeamPermission extends BaseModel {
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadProjectTeam,
],
update: [

View File

@@ -46,6 +46,7 @@ import EnableDocumentation from 'Common/Types/Database/EnableDocumentation';
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
Permission.ProjectMember,
],
read: [
Permission.ProjectOwner,
@@ -57,10 +58,12 @@ import EnableDocumentation from 'Common/Types/Database/EnableDocumentation';
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanDeleteWorkflow,
Permission.ProjectMember,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanDeleteWorkflow,
Permission.CanEditWorkflow,
],
})
@@ -83,6 +86,7 @@ export default class Workflow extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
Permission.ProjectMember,
],
read: [
Permission.ProjectOwner,
@@ -119,6 +123,7 @@ export default class Workflow extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
Permission.ProjectMember,
],
read: [
Permission.ProjectOwner,
@@ -149,6 +154,7 @@ export default class Workflow extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
Permission.ProjectMember,
],
read: [
Permission.ProjectOwner,
@@ -159,6 +165,7 @@ export default class Workflow extends BaseModel {
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanDeleteWorkflow,
Permission.CanEditWorkflow,
],
})
@@ -206,6 +213,7 @@ export default class Workflow extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
Permission.ProjectMember,
],
read: [
Permission.ProjectOwner,
@@ -216,6 +224,7 @@ export default class Workflow extends BaseModel {
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanDeleteWorkflow,
Permission.CanEditWorkflow,
],
})
@@ -237,6 +246,7 @@ export default class Workflow extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
Permission.ProjectMember,
],
read: [
Permission.ProjectOwner,
@@ -273,6 +283,7 @@ export default class Workflow extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
Permission.ProjectMember,
],
read: [
Permission.ProjectOwner,
@@ -355,6 +366,7 @@ export default class Workflow extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
Permission.ProjectMember,
],
read: [
Permission.ProjectOwner,
@@ -365,6 +377,7 @@ export default class Workflow extends BaseModel {
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanDeleteWorkflow,
Permission.CanEditWorkflow,
],
})
@@ -385,6 +398,7 @@ export default class Workflow extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
Permission.ProjectMember,
],
read: [
Permission.ProjectOwner,
@@ -395,6 +409,7 @@ export default class Workflow extends BaseModel {
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanDeleteWorkflow,
Permission.CanEditWorkflow,
],
})
@@ -417,6 +432,7 @@ export default class Workflow extends BaseModel {
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
Permission.ProjectMember,
],
read: [
Permission.ProjectOwner,
@@ -427,6 +443,7 @@ export default class Workflow extends BaseModel {
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanDeleteWorkflow,
Permission.CanEditWorkflow,
],
})

View File

@@ -20,49 +20,68 @@ router.get(
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
try {
logger.info('Request Headers: ');
logger.info(req.headers);
logger.info('Request Body: ');
logger.info(req.body);
const responseCode: number | undefined =
LocalCache.getNumber('TestServer', 'responseCode') || 200;
const responseTime: number | undefined =
LocalCache.getNumber('TestServer', 'responseTime') || 0;
const responseBody: string | undefined =
LocalCache.getString('TestServer', 'responseBody') || '';
let responseHeaders: JSONValue | undefined =
LocalCache.getJSON('TestServer', 'responseHeaders') || {};
logger.info('Response Code: ' + responseCode);
logger.info('Response Time: ' + responseTime);
logger.info('Response Body: ');
logger.info(responseBody);
logger.info('Response Headers: ');
logger.info(responseHeaders);
if (responseHeaders && typeof responseHeaders === Typeof.String) {
responseHeaders = JSON.parse(responseHeaders.toString());
}
if (responseTime > 0) {
await Sleep.sleep(responseTime);
}
// middleware marks the probe as alive.
// so we don't need to do anything here.
return Response.sendCustomResponse(
req,
res,
responseCode,
responseBody,
responseHeaders ? (responseHeaders as any) : {}
);
} catch (err) {
return next(err);
}
await returnResponse(req, res, next);
}
);
router.post(
'/',
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
await returnResponse(req, res, next);
}
);
const returnResponse: Function = async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
try {
logger.info('Request Headers: ');
logger.info(req.headers);
logger.info('Request Body: ');
logger.info(req.body);
const responseCode: number | undefined =
LocalCache.getNumber('TestServer', 'responseCode') || 200;
const responseTime: number | undefined =
LocalCache.getNumber('TestServer', 'responseTime') || 0;
const responseBody: string | undefined =
LocalCache.getString('TestServer', 'responseBody') || '';
let responseHeaders: JSONValue | undefined =
LocalCache.getJSON('TestServer', 'responseHeaders') || {};
logger.info('Response Code: ' + responseCode);
logger.info('Response Time: ' + responseTime);
logger.info('Response Body: ');
logger.info(responseBody);
logger.info('Response Headers: ');
logger.info(responseHeaders);
if (responseHeaders && typeof responseHeaders === Typeof.String) {
responseHeaders = JSON.parse(responseHeaders.toString());
}
if (responseTime > 0) {
await Sleep.sleep(responseTime);
}
// middleware marks the probe as alive.
// so we don't need to do anything here.
return Response.sendCustomResponse(
req,
res,
responseCode,
responseBody,
responseHeaders ? (responseHeaders as any) : {}
);
} catch (err) {
return next(err);
}
};
export default router;