Skip to content

Conversation

@LetItRock
Copy link
Contributor

@LetItRock LetItRock commented Jul 22, 2025

What changed? Why was the change needed?

Layouts Sync functionality and Editor in preview mode.
Refactored the Sync strategies and related so it is workflow agnostic and could be reused for any type of resource.

Screenshots

Screen.Recording.2025-07-22.at.10.19.57.mov
Screen.Recording.2025-07-22.at.10.18.49.mov
Screen.Recording.2025-07-22.at.10.17.51.mov

Summary by CodeRabbit

  • New Features

    • Added support for synchronizing layouts between environments, including detection and syncing of layout changes alongside workflows.
    • Enhanced environment diff and sync operations to include layouts as a resource type.
    • Introduced new user tracking for layouts, displaying who last updated a layout in API responses and dashboard UI.
    • Layout duplication and editing are now restricted to development environments for increased control.
  • Improvements

    • Standardized resource type handling and validation for environment sync and diff operations.
    • Improved dashboard UI to reflect environment-based access for layout editing and actions.
  • Bug Fixes

    • Fixed mapping and API responses to accurately reflect layout updater information.
  • Chores

    • Refactored and modularized sync strategies and operations for workflows and layouts, improving maintainability and extensibility.

@LetItRock LetItRock requested a review from scopsy July 22, 2025 08:25
@LetItRock LetItRock self-assigned this Jul 22, 2025
@linear
Copy link

linear bot commented Jul 22, 2025

@netlify
Copy link

netlify bot commented Jul 22, 2025

Deploy Preview for dashboard-v2-novu-staging canceled.

Name Link
🔨 Latest commit 032b885
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/687f4e867888d30008df9e21

@github-actions github-actions bot changed the title feat(api-service,dashboard): layouts sync functionality feat(api-service,dashboard): layouts sync functionality fixes NV-6207 Jul 22, 2025
@LetItRock
Copy link
Contributor Author

@coderabbitai summary

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 22, 2025

✅ Actions performed

Summary regeneration triggered.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 22, 2025

Walkthrough

This change introduces a generalized and extensible synchronization and diffing framework for both workflows and layouts across environments. It adds new base interfaces, adapters, comparators, normalizers, operations, and strategies for layouts, mirroring the existing workflow sync structure. The code also unifies resource type enums, enhances DTOs and entities with user tracking, and updates dashboard UI to restrict editing and duplication based on environment and layout origin.

Changes

File(s) / Path(s) Change Summary
.../dtos/diff-environment.dto.ts, .../types/sync.types.ts Standardized resource type enums in DTOs; added LAYOUT to ResourceTypeEnum.
.../environments.module.ts, .../usecases/diff-environment/diff-environment.usecase.ts, .../usecases/publish-environment/publish-environment.usecase.ts Refactored module imports to use new SyncModule; updated use cases to support both workflow and layout sync strategies.
.../usecases/sync-strategies/adapters/*.ts Added new adapters for repository, sync, delete, and comparator operations for workflows and layouts.
.../usecases/sync-strategies/base/interfaces/.ts, .../base/operations/.ts, .../base/index.ts Introduced base interfaces and abstract classes for repository, sync, delete, comparator, diff, and sync operations.
.../usecases/sync-strategies/builders/*.ts, .../constants/sync.constants.ts Refactored and centralized sync constants and utility builders.
.../usecases/sync-strategies/comparators/.ts, .../normalizers/.ts Added new normalizers and comparators for layouts; enhanced workflow comparator to include localization diffs.
.../usecases/sync-strategies/operations/*.ts Added new sync and diff operations for layouts and workflows, replacing old workflow-specific operations.
.../usecases/sync-strategies/sync.module.ts, .../layout-sync.strategy.ts, .../workflow-sync.strategy.ts Added consolidated SyncModule; introduced LayoutSyncStrategy; updated workflow sync strategy imports.
.../usecases/sync-strategies/types/*.ts Added types for normalized layouts and comparisons; updated workflow comparison type property.
.../layouts-v1/dtos/layout.dto.ts, .../layouts-v2/dtos/layout-response.dto.ts, .../layouts-v2/usecases/mapper.ts Added updatedBy field to layout DTOs and mapping logic for user tracking.
.../layouts-v1/usecases/create-layout/create-layout.use-case.ts, .../update-layout/update-layout.use-case.ts Added logic to set _updatedBy when creating or updating layouts.
.../layouts-v2/usecases/index.ts, .../sync-to-environment/, .../upsert-layout/.ts, .../upsert-layout/upsert-layout.usecase.ts Added layout sync-to-environment use case and command; enhanced upsert logic to optionally preserve layout IDs.
.../workflows-v2/usecases/sync-to-environment/sync-to-environment.usecase.ts Integrated layout sync into workflow sync for email steps referencing layouts.
.../workflows-v2/workflow.module.ts Exported UpsertWorkflowUseCase from the workflow module.
.../layouts-v2/usecases/sync-to-environment/index.ts Added barrel file for layout sync-to-environment exports.
.../dashboard/src/components/layouts/*.tsx Updated layout editor and row components to restrict editing/duplication based on environment and origin.
libs/dal/src/repositories/layout/layout.entity.ts, .../layout.repository.ts, .../layout.schema.ts Added _updatedBy and updatedBy user tracking to layout entities, schema, and repository; added new query methods for publishable layouts.
.../usecases/sync-strategies/workflow/constants/workflow-sync.constants.ts, .../workflow/index.ts, .../workflow/operations/*.ts Deleted workflow-specific constants, index, and operation files (now replaced by generalized base operations and adapters).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Dashboard
    participant API
    participant SyncModule
    participant WorkflowSyncStrategy
    participant LayoutSyncStrategy
    participant WorkflowSyncOperation
    participant LayoutSyncOperation
    participant RepositoryAdapter
    participant ComparatorAdapter

    User->>Dashboard: Initiates environment sync or diff
    Dashboard->>API: Calls sync/diff endpoint
    API->>SyncModule: Delegates to sync strategies
    SyncModule->>WorkflowSyncStrategy: Execute workflow sync/diff
    SyncModule->>LayoutSyncStrategy: Execute layout sync/diff
    WorkflowSyncStrategy->>WorkflowSyncOperation: Orchestrate workflow sync
    LayoutSyncStrategy->>LayoutSyncOperation: Orchestrate layout sync
    WorkflowSyncOperation->>RepositoryAdapter: Fetch workflows
    LayoutSyncOperation->>RepositoryAdapter: Fetch layouts
    WorkflowSyncOperation->>ComparatorAdapter: Compare workflows
    LayoutSyncOperation->>ComparatorAdapter: Compare layouts
    WorkflowSyncOperation->>API: Return workflow sync/diff result
    LayoutSyncOperation->>API: Return layout sync/diff result
    API->>Dashboard: Respond with results
Loading

Estimated code review effort

4 (~100 minutes)

Possibly related PRs

  • novuhq/novu#8608: Introduced initial environment diff and publish DTOs, commands, use cases, and workflow sync strategy; this PR extends and generalizes that foundation to support layouts and a unified sync framework.

Suggested labels

@novu/shared

Suggested reviewers

  • scopsy
  • djabarovgeorge

Poem

🐇
In the fields of sync and diff,
Layouts join the workflow riff.
Adapters, modules, enums aligned,
User tracks and types refined.
Dashboards check where edits go,
While rabbits hop—reviewers know!
Code now leaps from field to field,
Unified, robust, and gently healed.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c66b5b and 82d525f.

📒 Files selected for processing (65)
  • apps/api/src/app/environments-v2/dtos/diff-environment.dto.ts (3 hunks)
  • apps/api/src/app/environments-v2/environments.module.ts (1 hunks)
  • apps/api/src/app/environments-v2/types/sync.types.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/diff-environment/diff-environment.usecase.ts (2 hunks)
  • apps/api/src/app/environments-v2/usecases/publish-environment/publish-environment.usecase.ts (2 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/adapters/index.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/adapters/layout-comparator.adapter.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/adapters/layout-delete.adapter.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/adapters/layout-repository.adapter.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/adapters/layout-sync.adapter.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/adapters/workflow-comparator.adapter.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/adapters/workflow-delete.adapter.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/adapters/workflow-repository.adapter.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/adapters/workflow-sync.adapter.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/base/base-sync.strategy.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/base/index.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/base/interfaces/base-comparator.interface.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/base/interfaces/base-delete.interface.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/base/interfaces/base-repository.interface.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/base/interfaces/base-sync.interface.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/base/interfaces/index.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/base/operations/base-diff.operation.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/base/operations/base-sync.operation.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/base/operations/index.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/builders/diff-result.builder.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/builders/sync-result.builder.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/comparators/layout.comparator.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/comparators/workflow.comparator.ts (2 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/constants/sync.constants.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/index.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/layout-sync.strategy.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/normalizers/layout.normalizer.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/normalizers/workflow.normalizer.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/operations/layout-diff.operation.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/operations/layout-reposiotry.service.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/operations/layout-sync.operation.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/operations/workflow-diff.operation.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/operations/workflow-sync.operation.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/sync.module.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/types/layout-sync.types.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/types/workflow-sync.types.ts (2 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/workflow-sync.strategy.ts (1 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/workflow/constants/workflow-sync.constants.ts (0 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/workflow/index.ts (0 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/workflow/operations/workflow-diff.operation.ts (0 hunks)
  • apps/api/src/app/environments-v2/usecases/sync-strategies/workflow/operations/workflow-sync.operation.ts (0 hunks)
  • apps/api/src/app/layouts-v1/dtos/layout.dto.ts (2 hunks)
  • apps/api/src/app/layouts-v1/usecases/create-layout/create-layout.use-case.ts (1 hunks)
  • apps/api/src/app/layouts-v1/usecases/update-layout/update-layout.use-case.ts (1 hunks)
  • apps/api/src/app/layouts-v2/dtos/layout-response.dto.ts (2 hunks)
  • apps/api/src/app/layouts-v2/usecases/index.ts (2 hunks)
  • apps/api/src/app/layouts-v2/usecases/mapper.ts (1 hunks)
  • apps/api/src/app/layouts-v2/usecases/sync-to-environment/index.ts (1 hunks)
  • apps/api/src/app/layouts-v2/usecases/sync-to-environment/layout-sync-to-environment.command.ts (1 hunks)
  • apps/api/src/app/layouts-v2/usecases/sync-to-environment/layout-sync-to-environment.usecase.ts (1 hunks)
  • apps/api/src/app/layouts-v2/usecases/upsert-layout/upsert-layout.command.ts (3 hunks)
  • apps/api/src/app/layouts-v2/usecases/upsert-layout/upsert-layout.usecase.ts (1 hunks)
  • apps/api/src/app/workflows-v2/usecases/sync-to-environment/sync-to-environment.usecase.ts (4 hunks)
  • apps/api/src/app/workflows-v2/workflow.module.ts (1 hunks)
  • apps/dashboard/src/components/layouts/layout-editor-provider.tsx (2 hunks)
  • apps/dashboard/src/components/layouts/layout-editor.tsx (5 hunks)
  • apps/dashboard/src/components/layouts/layout-row.tsx (3 hunks)
  • libs/dal/src/repositories/layout/layout.entity.ts (2 hunks)
  • libs/dal/src/repositories/layout/layout.repository.ts (2 hunks)
  • libs/dal/src/repositories/layout/layout.schema.ts (2 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

WorkflowDiffOperation,
WorkflowRepositoryService,
} from './usecases/sync-strategies/workflow';
import { SyncModule } from './usecases/sync-strategies/sync.module';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created the sync module where all the sync strategies and operations are defined

import { LayoutComparator } from '../comparators/layout.comparator';

@Injectable()
export class LayoutComparatorAdapter implements IBaseComparator<LayoutEntity> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we will use this adapter pattern as the interface for actions on the resources like:

  • find resources
  • sync to target environment
  • delete resource
  • compare resources

import { IBaseRepositoryService, IBaseComparator } from '../interfaces';
import { capitalize } from '../../../../../shared/services/helper/helper.service';

export abstract class BaseDiffOperation<T> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the base diff operation abstract class, it is reused for both workflows and layouts

reason?: string;
}

export abstract class BaseSyncOperation<T> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base sync operation abstract class reused for both workflows and layouts

Comment on lines +69 to +79
for (const step of workflowDto.steps) {
if (step.type === StepTypeEnum.EMAIL && step.controlValues?.layoutId) {
await this.layoutSyncToEnvironmentUseCase.execute(
LayoutSyncToEnvironmentCommand.create({
user: command.user,
layoutIdOrInternalId: step.controlValues.layoutId as string,
targetEnvironmentId: command.targetEnvironmentId,
})
);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for every step in the workflow when layoutId is set we should sync it first before syncing workflow

* In the future, we can add more strategies here
*/
const strategies = [this.workflowSyncStrategy];
const strategies = [this.workflowSyncStrategy, this.layoutSyncStrategy];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder in terms of sequence and dependency, if it's better to promote layouts before workflows 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to have a specific order, as the workflow sync usecase handles layout syncing when it's used in the email step

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see, makes sense.

* Or are not relevant for the comparison
*/
normalizeLayout(layout: LayoutResponseDto): INormalizedLayout {
const { _id, updatedAt, updatedBy, createdAt, slug, isDefault, origin, type, variables, ...normalizedLayout } =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, if we want to recognize here if the default layout is changed, do we allow changing the default? In that case I would expect the field to be there

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no we don't allow changing the default

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got ya 💪

@LetItRock LetItRock merged commit bcabe2e into next Jul 22, 2025
35 checks passed
@LetItRock LetItRock deleted the nv-6207-layouts-sync-to-env branch July 22, 2025 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants