feat(api): add view configuration API routes (#13177)
* feat: add view_configurations feature flag - Add feature flag provider and hooks to admin dashboard - Add backend API endpoint for feature flags - Create view_configurations feature flag (disabled by default) - Update order list table to use legacy version when flag is disabled - Can be enabled with MEDUSA_FF_VIEW_CONFIGURATIONS=true env var * fix: naming * fix: feature flags unauthenticated * fix: add test * feat: add settings module * fix: deps * fix: cleanup * fix: add more tetsts * fix: rm changelog * fix: deps * fix: add settings module to default modules list * feat(api): add view configuration API routes - Add CRUD endpoints for view configurations - Add active view configuration management endpoints - Add feature flag middleware for view config routes - Add comprehensive integration tests - Add HTTP types for view configuration payloads and responses - Support system defaults and user-specific configurations - Enable setting views as active during create/update operations * fix: test * fix: test * fix: test * fix: change view configuration path * fix: tests * fix: remove manual settings module config from integration tests * fix: container typing * fix: workflows
This commit is contained in:
@@ -25,6 +25,7 @@ export * from "./region"
|
||||
export * from "./reservation"
|
||||
export * from "./return-reason"
|
||||
export * from "./sales-channel"
|
||||
export * from "./settings"
|
||||
export * from "./shipping-options"
|
||||
export * from "./shipping-profile"
|
||||
export * from "./stock-location"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./steps"
|
||||
export * from "./workflows"
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
CreateViewConfigurationDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
export type CreateViewConfigurationStepInput = CreateViewConfigurationDTO
|
||||
|
||||
export const createViewConfigurationStepId = "create-view-configuration"
|
||||
|
||||
export const createViewConfigurationStep = createStep(
|
||||
createViewConfigurationStepId,
|
||||
async (data: CreateViewConfigurationStepInput, { container }) => {
|
||||
const service = container.resolve(Modules.SETTINGS)
|
||||
const created = await service.createViewConfigurations(data)
|
||||
|
||||
return new StepResponse(created, { id: created.id })
|
||||
},
|
||||
async (compensateInput, { container }) => {
|
||||
if (!compensateInput?.id) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve(Modules.SETTINGS)
|
||||
await service.deleteViewConfigurations([compensateInput.id])
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./create-view-configuration"
|
||||
export * from "./update-view-configuration"
|
||||
export * from "./set-active-view-configuration"
|
||||
@@ -0,0 +1,59 @@
|
||||
import { ISettingsModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
export type SetActiveViewConfigurationStepInput = {
|
||||
id: string
|
||||
entity: string
|
||||
user_id: string
|
||||
}
|
||||
|
||||
export const setActiveViewConfigurationStepId = "set-active-view-configuration"
|
||||
|
||||
export const setActiveViewConfigurationStep = createStep(
|
||||
setActiveViewConfigurationStepId,
|
||||
async (input: SetActiveViewConfigurationStepInput, { container }) => {
|
||||
const service = container.resolve<ISettingsModuleService>(Modules.SETTINGS)
|
||||
|
||||
// Get the currently active view configuration for rollback
|
||||
const currentActiveView = await service.getActiveViewConfiguration(
|
||||
input.entity,
|
||||
input.user_id
|
||||
)
|
||||
|
||||
// Set the new view as active
|
||||
await service.setActiveViewConfiguration(
|
||||
input.entity,
|
||||
input.user_id,
|
||||
input.id
|
||||
)
|
||||
|
||||
return new StepResponse(input.id, {
|
||||
entity: input.entity,
|
||||
user_id: input.user_id,
|
||||
previousActiveViewId: currentActiveView?.id || null,
|
||||
})
|
||||
},
|
||||
async (compensateInput, { container }) => {
|
||||
if (!compensateInput) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<ISettingsModuleService>(Modules.SETTINGS)
|
||||
|
||||
if (compensateInput.previousActiveViewId) {
|
||||
// Restore the previous active view
|
||||
await service.setActiveViewConfiguration(
|
||||
compensateInput.entity,
|
||||
compensateInput.user_id,
|
||||
compensateInput.previousActiveViewId
|
||||
)
|
||||
} else {
|
||||
// If there was no previous active view, clear the active view
|
||||
await service.clearActiveViewConfiguration(
|
||||
compensateInput.entity,
|
||||
compensateInput.user_id
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
UpdateViewConfigurationDTO,
|
||||
ISettingsModuleService,
|
||||
ViewConfigurationDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
export type UpdateViewConfigurationStepInput = {
|
||||
id: string
|
||||
data: UpdateViewConfigurationDTO
|
||||
}
|
||||
|
||||
export const updateViewConfigurationStepId = "update-view-configuration"
|
||||
|
||||
export const updateViewConfigurationStep = createStep(
|
||||
updateViewConfigurationStepId,
|
||||
async (input: UpdateViewConfigurationStepInput, { container }) => {
|
||||
const service = container.resolve<ISettingsModuleService>(Modules.SETTINGS)
|
||||
|
||||
const currentState = await service.retrieveViewConfiguration(input.id)
|
||||
|
||||
const updated = await service.updateViewConfigurations(input.id, input.data)
|
||||
|
||||
return new StepResponse(updated, {
|
||||
id: input.id,
|
||||
previousState: currentState,
|
||||
})
|
||||
},
|
||||
async (compensateInput, { container }) => {
|
||||
if (!compensateInput?.id || !compensateInput?.previousState) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<ISettingsModuleService>(Modules.SETTINGS)
|
||||
|
||||
const { id, created_at, updated_at, ...restoreData } =
|
||||
compensateInput.previousState as ViewConfigurationDTO
|
||||
await service.updateViewConfigurations(compensateInput.id, restoreData)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
CreateViewConfigurationDTO,
|
||||
ViewConfigurationDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
createWorkflow,
|
||||
when,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import {
|
||||
createViewConfigurationStep,
|
||||
setActiveViewConfigurationStep,
|
||||
} from "../steps"
|
||||
|
||||
export type CreateViewConfigurationWorkflowInput =
|
||||
CreateViewConfigurationDTO & {
|
||||
set_active?: boolean
|
||||
}
|
||||
|
||||
export const createViewConfigurationWorkflowId = "create-view-configuration"
|
||||
|
||||
export const createViewConfigurationWorkflow = createWorkflow(
|
||||
createViewConfigurationWorkflowId,
|
||||
(
|
||||
input: WorkflowData<CreateViewConfigurationWorkflowInput>
|
||||
): WorkflowResponse<ViewConfigurationDTO> => {
|
||||
const viewConfig = createViewConfigurationStep(input)
|
||||
|
||||
when({ input, viewConfig }, ({ input }) => {
|
||||
return !!input.set_active && !!input.user_id
|
||||
}).then(() => {
|
||||
setActiveViewConfigurationStep({
|
||||
id: viewConfig.id,
|
||||
entity: viewConfig.entity,
|
||||
user_id: input.user_id as string,
|
||||
})
|
||||
})
|
||||
|
||||
return new WorkflowResponse(viewConfig)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./create-view-configuration"
|
||||
export * from "./update-view-configuration"
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
UpdateViewConfigurationDTO,
|
||||
ViewConfigurationDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
createWorkflow,
|
||||
when,
|
||||
transform,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import {
|
||||
updateViewConfigurationStep,
|
||||
setActiveViewConfigurationStep,
|
||||
} from "../steps"
|
||||
|
||||
export type UpdateViewConfigurationWorkflowInput = {
|
||||
id: string
|
||||
set_active?: boolean
|
||||
} & UpdateViewConfigurationDTO
|
||||
|
||||
export const updateViewConfigurationWorkflowId = "update-view-configuration"
|
||||
|
||||
export const updateViewConfigurationWorkflow = createWorkflow(
|
||||
updateViewConfigurationWorkflowId,
|
||||
(
|
||||
input: WorkflowData<UpdateViewConfigurationWorkflowInput>
|
||||
): WorkflowResponse<ViewConfigurationDTO> => {
|
||||
const updateData = transform({ input }, ({ input }) => {
|
||||
const { id, set_active, ...data } = input
|
||||
return data
|
||||
})
|
||||
|
||||
const viewConfig = updateViewConfigurationStep({
|
||||
id: input.id,
|
||||
data: updateData,
|
||||
})
|
||||
|
||||
when({ input, viewConfig }, ({ input, viewConfig }) => {
|
||||
return !!input.set_active && !!viewConfig.user_id
|
||||
}).then(() => {
|
||||
setActiveViewConfigurationStep({
|
||||
id: viewConfig.id,
|
||||
entity: viewConfig.entity,
|
||||
user_id: viewConfig.user_id as string,
|
||||
})
|
||||
})
|
||||
|
||||
return new WorkflowResponse(viewConfig)
|
||||
}
|
||||
)
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
IPromotionModuleService,
|
||||
IRegionModuleService,
|
||||
ISalesChannelModuleService,
|
||||
ISettingsModuleService,
|
||||
IStockLocationService,
|
||||
IStoreModuleService,
|
||||
ITaxModuleService,
|
||||
@@ -74,6 +75,7 @@ declare module "@medusajs/types" {
|
||||
[Modules.FILE]: IFileModuleService
|
||||
[Modules.NOTIFICATION]: INotificationModuleService
|
||||
[Modules.LOCKING]: ILockingModule
|
||||
[Modules.SETTINGS]: ISettingsModuleService
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,4 +43,5 @@ export * from "./tax-provider"
|
||||
export * from "./tax-rate"
|
||||
export * from "./tax-region"
|
||||
export * from "./user"
|
||||
export * from "./view-configuration"
|
||||
export * from "./workflow-execution"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./responses"
|
||||
export * from "./queries"
|
||||
export * from "./payloads"
|
||||
@@ -0,0 +1,108 @@
|
||||
export interface AdminCreateViewConfiguration {
|
||||
/**
|
||||
* The entity this configuration is for (e.g., "order", "product").
|
||||
*/
|
||||
entity: string
|
||||
/**
|
||||
* The name of the view configuration.
|
||||
*/
|
||||
name?: string
|
||||
/**
|
||||
* Whether this is a system default configuration.
|
||||
*/
|
||||
is_system_default?: boolean
|
||||
/**
|
||||
* Whether to set this view as the active view after creation.
|
||||
*/
|
||||
set_active?: boolean
|
||||
/**
|
||||
* The view configuration settings.
|
||||
*/
|
||||
configuration: {
|
||||
/**
|
||||
* The list of visible column IDs.
|
||||
*/
|
||||
visible_columns: string[]
|
||||
/**
|
||||
* The order of columns.
|
||||
*/
|
||||
column_order: string[]
|
||||
/**
|
||||
* Custom column widths.
|
||||
*/
|
||||
column_widths?: Record<string, number>
|
||||
/**
|
||||
* Active filters for the view.
|
||||
*/
|
||||
filters?: Record<string, any>
|
||||
/**
|
||||
* Sorting configuration.
|
||||
*/
|
||||
sorting?: {
|
||||
id: string
|
||||
desc: boolean
|
||||
} | null
|
||||
/**
|
||||
* Search query for the view.
|
||||
*/
|
||||
search?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface AdminUpdateViewConfiguration {
|
||||
/**
|
||||
* The name of the view configuration.
|
||||
*/
|
||||
name?: string
|
||||
/**
|
||||
* Whether this is a system default configuration.
|
||||
*/
|
||||
is_system_default?: boolean
|
||||
/**
|
||||
* Whether to set this view as the active view after update.
|
||||
*/
|
||||
set_active?: boolean
|
||||
/**
|
||||
* The view configuration settings.
|
||||
*/
|
||||
configuration?: {
|
||||
/**
|
||||
* The list of visible column IDs.
|
||||
*/
|
||||
visible_columns?: string[]
|
||||
/**
|
||||
* The order of columns.
|
||||
*/
|
||||
column_order?: string[]
|
||||
/**
|
||||
* Custom column widths.
|
||||
*/
|
||||
column_widths?: Record<string, number>
|
||||
/**
|
||||
* Active filters for the view.
|
||||
*/
|
||||
filters?: Record<string, any>
|
||||
/**
|
||||
* Sorting configuration.
|
||||
*/
|
||||
sorting?: {
|
||||
id: string
|
||||
desc: boolean
|
||||
} | null
|
||||
/**
|
||||
* Search query for the view.
|
||||
*/
|
||||
search?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface AdminSetActiveViewConfiguration {
|
||||
/**
|
||||
* The entity to set the active view for.
|
||||
*/
|
||||
entity: string
|
||||
/**
|
||||
* The ID of the view configuration to set as active, or null to clear the active view.
|
||||
*/
|
||||
view_configuration_id: string | null
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { BaseFilterable, OperatorMap } from "../../../dal"
|
||||
import { FindParams, SelectParams } from "../../common"
|
||||
|
||||
export interface AdminGetViewConfigurationParams extends SelectParams {}
|
||||
|
||||
export interface AdminGetViewConfigurationsParams
|
||||
extends FindParams,
|
||||
BaseFilterable<AdminGetViewConfigurationsParams> {
|
||||
/**
|
||||
* IDs to filter view configurations by.
|
||||
*/
|
||||
id?: string | string[]
|
||||
/**
|
||||
* Entity to filter by.
|
||||
*/
|
||||
entity?: string | string[]
|
||||
/**
|
||||
* Name to filter by.
|
||||
*/
|
||||
name?: string | string[]
|
||||
/**
|
||||
* User ID to filter by.
|
||||
*/
|
||||
user_id?: string | string[] | null
|
||||
/**
|
||||
* Filter by system default status.
|
||||
*/
|
||||
is_system_default?: boolean
|
||||
/**
|
||||
* Date filters for when the view configuration was created.
|
||||
*/
|
||||
created_at?: OperatorMap<string>
|
||||
/**
|
||||
* Date filters for when the view configuration was updated.
|
||||
*/
|
||||
updated_at?: OperatorMap<string>
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { DeleteResponse, PaginatedResponse } from "../../common"
|
||||
|
||||
interface AdminViewConfiguration {
|
||||
/**
|
||||
* The view configuration's ID.
|
||||
*/
|
||||
id: string
|
||||
/**
|
||||
* The entity this configuration is for (e.g., "order", "product").
|
||||
*/
|
||||
entity: string
|
||||
/**
|
||||
* The name of the view configuration.
|
||||
*/
|
||||
name: string | null
|
||||
/**
|
||||
* The ID of the user who owns this configuration, or null for system defaults.
|
||||
*/
|
||||
user_id: string | null
|
||||
/**
|
||||
* Whether this is a system default configuration.
|
||||
*/
|
||||
is_system_default: boolean
|
||||
/**
|
||||
* The view configuration settings.
|
||||
*/
|
||||
configuration: {
|
||||
/**
|
||||
* The list of visible column IDs.
|
||||
*/
|
||||
visible_columns: string[]
|
||||
/**
|
||||
* The order of columns.
|
||||
*/
|
||||
column_order: string[]
|
||||
/**
|
||||
* Custom column widths.
|
||||
*/
|
||||
column_widths?: Record<string, number>
|
||||
/**
|
||||
* Active filters for the view.
|
||||
*/
|
||||
filters?: Record<string, any>
|
||||
/**
|
||||
* Sorting configuration.
|
||||
*/
|
||||
sorting?: {
|
||||
id: string
|
||||
desc: boolean
|
||||
} | null
|
||||
/**
|
||||
* Search query for the view.
|
||||
*/
|
||||
search?: string
|
||||
}
|
||||
/**
|
||||
* The date the view configuration was created.
|
||||
*/
|
||||
created_at: Date
|
||||
/**
|
||||
* The date the view configuration was updated.
|
||||
*/
|
||||
updated_at: Date
|
||||
}
|
||||
|
||||
export interface AdminViewConfigurationResponse {
|
||||
/**
|
||||
* The view configuration's details.
|
||||
*/
|
||||
view_configuration: AdminViewConfiguration | null
|
||||
}
|
||||
|
||||
export type AdminViewConfigurationListResponse = PaginatedResponse<{
|
||||
/**
|
||||
* The list of view configurations.
|
||||
*/
|
||||
view_configurations: AdminViewConfiguration[]
|
||||
}>
|
||||
|
||||
export type AdminViewConfigurationDeleteResponse = DeleteResponse<"view_configuration">
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./admin"
|
||||
@@ -111,9 +111,9 @@ export interface UserPreferenceDTO {
|
||||
}
|
||||
|
||||
/**
|
||||
* The filters to apply on the retrieved view configurations.
|
||||
* Partial filters for view configuration fields.
|
||||
*/
|
||||
export interface FilterableViewConfigurationProps extends BaseFilterable<ViewConfigurationDTO> {
|
||||
export interface ViewConfigurationFilterableFields {
|
||||
/**
|
||||
* The IDs to filter by.
|
||||
*/
|
||||
@@ -140,6 +140,21 @@ export interface FilterableViewConfigurationProps extends BaseFilterable<ViewCon
|
||||
name?: string | string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The filters to apply on the retrieved view configurations.
|
||||
*/
|
||||
export interface FilterableViewConfigurationProps extends ViewConfigurationFilterableFields {
|
||||
/**
|
||||
* An array of filters to apply on the entity, where each item in the array is joined with an "and" condition.
|
||||
*/
|
||||
$and?: (ViewConfigurationFilterableFields | FilterableViewConfigurationProps)[]
|
||||
|
||||
/**
|
||||
* An array of filters to apply on the entity, where each item in the array is joined with an "or" condition.
|
||||
*/
|
||||
$or?: (ViewConfigurationFilterableFields | FilterableViewConfigurationProps)[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The filters to apply on the retrieved user preferences.
|
||||
*/
|
||||
|
||||
@@ -8,14 +8,14 @@ export interface CreateViewConfigurationDTO {
|
||||
entity: string
|
||||
|
||||
/**
|
||||
* The name of the configuration.
|
||||
* The name of the configuration. Required unless creating a system default.
|
||||
*/
|
||||
name: string
|
||||
name?: string
|
||||
|
||||
/**
|
||||
* The user ID this configuration belongs to.
|
||||
* The user ID this configuration belongs to. Can be null for system defaults.
|
||||
*/
|
||||
user_id?: string
|
||||
user_id?: string | null
|
||||
|
||||
/**
|
||||
* Whether this is a system default configuration.
|
||||
@@ -131,4 +131,4 @@ export interface UpdateUserPreferenceDTO {
|
||||
* The preference value.
|
||||
*/
|
||||
value: any
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user