diff --git a/.changeset/long-cherries-eat.md b/.changeset/long-cherries-eat.md new file mode 100644 index 000000000..d30268628 --- /dev/null +++ b/.changeset/long-cherries-eat.md @@ -0,0 +1,5 @@ +--- +"app-avatax": patch +--- + +Remove feature flag for client logs. After this change logs are enabled by default. diff --git a/apps/avatax/.env.example b/apps/avatax/.env.example index d80acf660..cbdcdd49e 100644 --- a/apps/avatax/.env.example +++ b/apps/avatax/.env.example @@ -34,11 +34,12 @@ SECRET_KEY= # E2E_USER_PASSWORD= -FF_ENABLE_EXPERIMENTAL_LOGS=true -DYNAMODB_LOGS_ITEM_TTL_IN_DAYS=30 +# DYNAMODB_LOGS_ITEM_TTL_IN_DAYS=14 - time to live for logs in DynamoDB DYNAMODB_LOGS_TABLE_NAME=avatax-client-logs AWS_REGION=localhost AWS_ENDPOINT_URL=http://localhost:8000 +AWS_ACCESS_KEY_ID=... +AWS_SECRET_ACCESS_KEY=... -MANIFEST_APP_ID=saleor.app.avatax \ No newline at end of file +MANIFEST_APP_ID=saleor.app.avatax diff --git a/apps/avatax/src/env.ts b/apps/avatax/src/env.ts index abecb1109..435956520 100644 --- a/apps/avatax/src/env.ts +++ b/apps/avatax/src/env.ts @@ -19,15 +19,13 @@ export const env = createEnv({ APP_IFRAME_BASE_URL: z.string().optional(), APP_LOG_LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).default("info"), AVATAX_CLIENT_TIMEOUT: z.coerce.number().optional().default(15000), - // TODO: make them required once we remove `FF_ENABLE_EXPERIMENTAL_LOGS` - AWS_ACCESS_KEY_ID: z.string().optional(), - AWS_REGION: z.string().optional(), - AWS_SECRET_ACCESS_KEY: z.string().optional(), - DYNAMODB_LOGS_ITEM_TTL_IN_DAYS: z.coerce.number().optional(), - DYNAMODB_LOGS_TABLE_NAME: z.string().optional(), + AWS_ACCESS_KEY_ID: z.string(), + AWS_REGION: z.string(), + AWS_SECRET_ACCESS_KEY: z.string(), + DYNAMODB_LOGS_ITEM_TTL_IN_DAYS: z.coerce.number().positive().optional().default(14), + DYNAMODB_LOGS_TABLE_NAME: z.string(), E2E_USER_NAME: z.string().optional(), E2E_USER_PASSWORD: z.string().optional(), - FF_ENABLE_EXPERIMENTAL_LOGS: booleanSchema.optional().default("false"), FILE_APL_PATH: z.string().optional(), MANIFEST_APP_ID: z.string().optional().default("saleor.app.avatax"), OTEL_ENABLED: booleanSchema.optional().default("false"), @@ -59,7 +57,6 @@ export const env = createEnv({ E2E_USER_NAME: process.env.E2E_USER_NAME, E2E_USER_PASSWORD: process.env.E2E_USER_PASSWORD, ENV: process.env.ENV, - FF_ENABLE_EXPERIMENTAL_LOGS: process.env.FF_ENABLE_EXPERIMENTAL_LOGS, FILE_APL_PATH: process.env.FILE_APL_PATH, MANIFEST_APP_ID: process.env.MANIFEST_APP_ID, NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, diff --git a/apps/avatax/src/modules/client-logs/client-logs-feature-config.ts b/apps/avatax/src/modules/client-logs/client-logs-feature-config.ts deleted file mode 100644 index 016667c3b..000000000 --- a/apps/avatax/src/modules/client-logs/client-logs-feature-config.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { z } from "zod"; - -import { env } from "@/env"; - -const schema = z - .object({ - isEnabled: z.boolean({ coerce: true }).optional().default(false), - dynamoTableName: z.string().optional(), - ttlInDays: z - .number({ - coerce: true, - errorMap(issue) { - return { message: "Provide value in days" }; - }, - }) - .optional() - .default(30), - }) - .superRefine((values, ctx) => { - if (values.isEnabled === true) { - if (!values.dynamoTableName) { - ctx.addIssue({ - code: "custom", - message: "When logs are enabled, DYNAMODB_LOGS_TABLE_NAME must be provided", - }); - } - } else { - return; - } - }); - -export const clientLogsFeatureConfig = schema.parse({ - isEnabled: env.FF_ENABLE_EXPERIMENTAL_LOGS, - dynamoTableName: env.DYNAMODB_LOGS_TABLE_NAME, - ttlInDays: env.DYNAMODB_LOGS_ITEM_TTL_IN_DAYS, -}); diff --git a/apps/avatax/src/modules/client-logs/client-logs.router.ts b/apps/avatax/src/modules/client-logs/client-logs.router.ts index 4838f683c..9dd04b3d3 100644 --- a/apps/avatax/src/modules/client-logs/client-logs.router.ts +++ b/apps/avatax/src/modules/client-logs/client-logs.router.ts @@ -1,11 +1,8 @@ -import * as Sentry from "@sentry/nextjs"; import { TRPCError } from "@trpc/server"; -import { err, ok } from "neverthrow"; import { z } from "zod"; -import { createLogger } from "@/logger"; +import { env } from "@/env"; import { type ClientLogValue } from "@/modules/client-logs/client-log"; -import { clientLogsFeatureConfig } from "@/modules/client-logs/client-logs-feature-config"; import { createLogsDocumentClient, createLogsDynamoClient, @@ -16,72 +13,41 @@ import { router } from "@/modules/trpc/trpc-server"; import { ClientLogDynamoEntityFactory, LogsTable } from "./dynamo-schema"; -// TODO: Remove this lazy method once feature is not behind feature flag -const getLogsRepository = () => { - const logger = createLogger("getLogsRepository"); - - if (!clientLogsFeatureConfig.dynamoTableName) { - logger.warn("DYNAMODB_LOGS_TABLE_NAME is not set."); - - return err(new Error("DYNAMODB_LOGS_TABLE_NAME is not set.")); - } - - const logsTable = LogsTable.create({ - documentClient: createLogsDocumentClient(createLogsDynamoClient()), - tableName: clientLogsFeatureConfig.dynamoTableName, - }); - const logByDateEntity = ClientLogDynamoEntityFactory.createLogByDate(logsTable); - const logByCheckoutOrOrderId = - ClientLogDynamoEntityFactory.createLogByCheckoutOrOrderId(logsTable); - - return ok( - new LogsRepositoryDynamodb({ - logsTable, - logByDateEntity, - logByCheckoutOrOrderId: logByCheckoutOrOrderId, - }), - ); -}; - -const procedureWithFlag = protectedClientProcedure.use(({ ctx, next }) => { - if (!clientLogsFeatureConfig.isEnabled) { - throw new TRPCError({ - cause: "Feature disabled", - code: "FORBIDDEN", - message: "Feature is disabled", +const procedureWithLogsRepository = protectedClientProcedure.use(({ ctx, next }) => { + try { + const logsTable = LogsTable.create({ + documentClient: createLogsDocumentClient(createLogsDynamoClient()), + tableName: env.DYNAMODB_LOGS_TABLE_NAME, }); - } - - const logsRepositoryResult = getLogsRepository(); - - if (logsRepositoryResult.isErr()) { - Sentry.captureException(logsRepositoryResult.error); - + const logByDateEntity = ClientLogDynamoEntityFactory.createLogByDate(logsTable); + const logByCheckoutOrOrderId = + ClientLogDynamoEntityFactory.createLogByCheckoutOrOrderId(logsTable); + + return next({ + ctx: { + ...ctx, + logsRepository: new LogsRepositoryDynamodb({ + logsTable, + logByDateEntity, + logByCheckoutOrOrderId, + }), + }, + }); + } catch (e) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "Logs unavailable, contact support", + message: "Logs are not available, contact Saleor support", }); } - - return next({ - ctx: { - ...ctx, - logsRepository: logsRepositoryResult.value, - }, - }); }); /** - * TODO: Implement pagination * * Router that fetches logs in the frontend. * To write log, use directly repository */ export const clientLogsRouter = router({ - isEnabled: protectedClientProcedure.query(({ ctx }) => { - return clientLogsFeatureConfig.isEnabled; - }), - getByDate: procedureWithFlag + getByDate: procedureWithLogsRepository .input( z.object({ startDate: z.string().datetime(), @@ -115,7 +81,7 @@ export const clientLogsRouter = router({ }; }, ), - getByCheckoutOrOrderId: procedureWithFlag + getByCheckoutOrOrderId: procedureWithLogsRepository .input( z.object({ checkoutOrOrderId: z.string(), diff --git a/apps/avatax/src/modules/client-logs/dynamo-schema.ts b/apps/avatax/src/modules/client-logs/dynamo-schema.ts index b0c324317..9ee876549 100644 --- a/apps/avatax/src/modules/client-logs/dynamo-schema.ts +++ b/apps/avatax/src/modules/client-logs/dynamo-schema.ts @@ -2,7 +2,7 @@ import { type DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb"; import { Entity, number, schema, string, Table } from "dynamodb-toolbox"; import { ulid } from "ulid"; -import { clientLogsFeatureConfig } from "@/modules/client-logs/client-logs-feature-config"; +import { env } from "@/env"; export class LogsTable extends Table< { @@ -61,7 +61,7 @@ export class LogsTable extends Table< } static getDefaultTTL() { - const daysUntilExpire = clientLogsFeatureConfig.ttlInDays; + const daysUntilExpire = env.DYNAMODB_LOGS_ITEM_TTL_IN_DAYS; const today = new Date(); // Add today + days until expire, export to UNIX epoch timestamp diff --git a/apps/avatax/src/modules/client-logs/log-writer-factory.ts b/apps/avatax/src/modules/client-logs/log-writer-factory.ts index 431616c5c..68e93c6c5 100644 --- a/apps/avatax/src/modules/client-logs/log-writer-factory.ts +++ b/apps/avatax/src/modules/client-logs/log-writer-factory.ts @@ -1,15 +1,11 @@ -import { clientLogsFeatureConfig } from "@/modules/client-logs/client-logs-feature-config"; +import { env } from "@/env"; +import { BaseError } from "@/error"; import { createLogsDocumentClient, createLogsDynamoClient, } from "@/modules/client-logs/dynamo-client"; import { ClientLogDynamoEntityFactory, LogsTable } from "@/modules/client-logs/dynamo-schema"; -import { - DynamoDbLogWriter, - ILogWriter, - LogWriterContext, - NoopLogWriter, -} from "@/modules/client-logs/log-writer"; +import { DynamoDbLogWriter, ILogWriter, LogWriterContext } from "@/modules/client-logs/log-writer"; import { LogsRepositoryDynamodb } from "@/modules/client-logs/logs-repository"; export interface ILogWriterFactory { @@ -20,30 +16,31 @@ export interface ILogWriterFactory { * Depending on static config, create an ILogWriter instance */ export class LogWriterFactory implements ILogWriterFactory { - private createDynamoDbWriter(context: LogWriterContext): ILogWriter { - const dynamoClient = createLogsDynamoClient(); - const logsTable = LogsTable.create({ - documentClient: createLogsDocumentClient(dynamoClient), - tableName: clientLogsFeatureConfig.dynamoTableName!, // If not set, it will throw earlier - }); - const repository = new LogsRepositoryDynamodb({ - logsTable, - logByCheckoutOrOrderId: ClientLogDynamoEntityFactory.createLogByCheckoutOrOrderId(logsTable), - logByDateEntity: ClientLogDynamoEntityFactory.createLogByDate(logsTable), - }); + static ErrorCreatingLogWriterError = BaseError.subclass("ErrorCreatingLogWriterError"); - return new DynamoDbLogWriter(repository, context); - } + private createDynamoDbWriter(context: LogWriterContext): ILogWriter { + try { + const dynamoClient = createLogsDynamoClient(); + const logsTable = LogsTable.create({ + documentClient: createLogsDocumentClient(dynamoClient), + tableName: env.DYNAMODB_LOGS_TABLE_NAME, + }); + const repository = new LogsRepositoryDynamodb({ + logsTable, + logByCheckoutOrOrderId: + ClientLogDynamoEntityFactory.createLogByCheckoutOrOrderId(logsTable), + logByDateEntity: ClientLogDynamoEntityFactory.createLogByDate(logsTable), + }); - private createNoopWriter(): ILogWriter { - return new NoopLogWriter(); + return new DynamoDbLogWriter(repository, context); + } catch (e) { + throw new LogWriterFactory.ErrorCreatingLogWriterError("Failed to create DynamoDbLogWriter", { + cause: e, + }); + } } createWriter(context: LogWriterContext): ILogWriter { - if (clientLogsFeatureConfig.isEnabled) { - return this.createDynamoDbWriter(context); - } else { - return this.createNoopWriter(); - } + return this.createDynamoDbWriter(context); } } diff --git a/apps/avatax/src/pages/configuration.tsx b/apps/avatax/src/pages/configuration.tsx index aaa0c6f83..d0677efa0 100644 --- a/apps/avatax/src/pages/configuration.tsx +++ b/apps/avatax/src/pages/configuration.tsx @@ -2,8 +2,6 @@ import { useAppBridge } from "@saleor/app-sdk/app-bridge"; import { Box, Button, Text } from "@saleor/macaw-ui"; import { useRouter } from "next/router"; -import { trpcClient } from "@/modules/trpc/trpc-client"; - import { ChannelSection } from "../modules/channel-configuration/ui/channel-section"; import { ProvidersSection } from "../modules/provider-connections/ui/providers-section"; import { AppPageLayout } from "../modules/ui/app-page-layout"; @@ -11,7 +9,6 @@ import { Section } from "../modules/ui/app-section"; import { MatcherSection } from "../modules/ui/matcher-section"; const Header = () => { - const { data: logsEnabled } = trpcClient.clientLogs.isEnabled.useQuery(); const { push } = useRouter(); return ( @@ -19,7 +16,7 @@ const Header = () => { Configure the app by connecting to AvaTax. You can connect to multiple accounts. - {logsEnabled && } + ); }; diff --git a/apps/avatax/src/pages/logs.tsx b/apps/avatax/src/pages/logs.tsx index 344adf0cb..87a27bad2 100644 --- a/apps/avatax/src/pages/logs.tsx +++ b/apps/avatax/src/pages/logs.tsx @@ -1,6 +1,5 @@ import { useAppBridge } from "@saleor/app-sdk/app-bridge"; import { Box } from "@saleor/macaw-ui"; -import React from "react"; import { LogsBrowser } from "@/modules/client-logs/ui/logs-browser"; import { AppBreadcrumbs } from "@/modules/ui/app-breadcrumbs"; @@ -8,7 +7,7 @@ import { AppBreadcrumbs } from "@/modules/ui/app-breadcrumbs"; import { Section } from "../modules/ui/app-section"; const Header = () => { - return Check App logs (up to last 100); + return Check App logs; }; const ConfigurationPage = () => { diff --git a/apps/avatax/src/setup-tests.ts b/apps/avatax/src/setup-tests.ts index fff045cbc..b7dec1f94 100644 --- a/apps/avatax/src/setup-tests.ts +++ b/apps/avatax/src/setup-tests.ts @@ -2,6 +2,10 @@ import { vi } from "vitest"; vi.stubEnv("DYNAMODB_LOGS_ITEM_TTL_IN_DAYS", "7"); vi.stubEnv("SECRET_KEY", "test_secret_key"); +vi.stubEnv("DYNAMODB_LOGS_TABLE_NAME", "test-table"); +vi.stubEnv("AWS_REGION", "test"); +vi.stubEnv("AWS_ACCESS_KEY_ID", "test-id"); +vi.stubEnv("AWS_SECRET_ACCESS_KEY", "test-key"); /** * Add test setup logic here