Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): storytelling ga support [VIZ-1180] #1425

Merged
merged 5 commits into from
Feb 27, 2025

Conversation

hexaforce
Copy link
Contributor

@hexaforce hexaforce commented Feb 14, 2025

Overview

Add Google Analytics parameters to the Story.

Notion:#1425

What I've done

I added the enableGa and trackingId parameters to the Story parameters, just like in the current project, and I also added the parameters to updateStory.

type Story implements Node {
  id: ID!
  title: String!
  alias: String!
  propertyId: ID!
  property: Property
  pages: [StoryPage!]!
  publishmentStatus: PublishmentStatus!
  createdAt: DateTime!
  updatedAt: DateTime!
  publishedAt: DateTime
  sceneId: ID!
  scene: Scene
  panelPosition: Position!
  bgColor: String
  isBasicAuthActive: Boolean!
  basicAuthUsername: String!
  basicAuthPassword: String!
  publicTitle: String!
  publicDescription: String!
  publicImage: String!
  publicNoIndex: Boolean!
+  enableGa: Boolean!
+  trackingId: String!
}

input UpdateStoryInput {
  sceneId: ID!
  storyId: ID!
  title: String
  index: Int
  panelPosition: Position
  bgColor: String

  # Publishment
  isBasicAuthActive: Boolean
  basicAuthUsername: String
  basicAuthPassword: String
  alias: String
  publicTitle: String
  publicDescription: String
  publicImage: String
  publicNoIndex: Boolean
  deletePublicImage: Boolean

+  # Google Analytics
+  enableGa: Boolean
+  trackingId: String
}

What I haven't done

How I tested

added enableGa and trackingId to the existing test code.

https://github.com/reearth/reearth-visualizer/pull/1425/files#diff-8f0c77ec34d3e5a7b00a586b79ff265e70a9ffc641a7d4eb568fc7c69be12616R15

Which point I want you to review particularly

Memo

Fix the front-end query here

web/src/services/gql/fragments/story.ts

export const storyFragment = gql`
  fragment StoryFragment on Story {
    id
    title
    panelPosition
    bgColor
    isBasicAuthActive
    basicAuthUsername
    basicAuthPassword
    alias
    publicTitle
    publicDescription
    publishmentStatus
    publicImage
    publicNoIndex
+   enableGa
+   trackingId
    pages {
      ...StoryPageFragment
    }
  }
`;

enableGa and trackingId will be added to UpdateStoryInput.

+   UpdateStoryInput.enableGa = ????
+   UpdateStoryInput.trackingId = ????

export const UPDATE_STORY = gql(`
  mutation UpdateStory($input: UpdateStoryInput!) {
    updateStory(input: $input) {
      story {
        id
      }
    }
  }
`);

Summary by CodeRabbit

  • New Features

    • Introduced new story settings for Google Analytics tracking, allowing users to configure tracking options via enhanced story endpoints.
  • Refactor

    • Streamlined server initialization and configuration management.
    • Improved middleware logic to support disabled authentication modes.
  • Tests

    • Expanded end-to-end tests to cover full lifecycle operations for stories, pages, and blocks.
    • Updated pagination tests to reflect revised project counts.

Copy link

coderabbitai bot commented Feb 14, 2025

Walkthrough

This pull request refactors server initialization and configuration in end-to-end tests, updating function signatures and renaming functions to streamline setups. It introduces a dedicated disabled authentication configuration variable and consolidates server start logic. In addition, the changes add new Google Analytics tracking fields (enableGa and trackingId) to the GraphQL schema and related data models, resolvers, and use cases. Test cases for projects as well as comprehensive CRUD operations for stories, pages, and blocks are updated accordingly. Minor adjustments in authentication middleware and logging further refine the overall behavior.

Changes

File(s) Change Summary
server/e2e/common.go, server/e2e/gql_asset_test.go, server/e2e/gql_me_test.go, server/e2e/gql_project_test.go, server/e2e/mock_test.go, server/e2e/ping_test.go Refactored server initialization: centralized configuration via a new variable (disabledAuthConfig), renamed functions (e.g., StartServerServer, StartServerAndReposServerAndRepos), removed explicit config objects, and updated expected test values.
server/e2e/gql_storytelling_test.go Introduced a comprehensive suite of new CRUD tests for stories, pages, and blocks; updated mutation parameters to include GA fields; removed obsolete helper functions.
server/gql/storytelling.graphql Added non-nullable enableGa and trackingId fields to the Story type and updated UpdateStoryInput to include these fields as optional.
server/internal/adapter/gql/gqlmodel/convert_storytelling.go, server/internal/adapter/gql/gqlmodel/models_gen.go, server/internal/adapter/gql/resolver_mutation_storytelling.go, server/internal/adapter/gql/generated.go Expanded GraphQL models and resolvers to incorporate Google Analytics fields (EnableGa, TrackingID) across story types and update inputs.
server/internal/infrastructure/mongo/mongodoc/storytelling.go, server/internal/usecase/interactor/storytelling.go, server/internal/usecase/interfaces/story.go Integrated new GA fields into database document models and updated the use case interfaces/methods for handling story updates.
server/pkg/storytelling/story.go, server/pkg/storytelling/story_bulider.go Extended story data structures with GA fields; added corresponding getter, setter, and builder methods.
server/internal/app/app.go, server/internal/app/auth_client.go Modified authentication: added a dummy authentication middleware (AuthMiddlewareDummy) for cases with disabled auth and adjusted logging to suppress details for "e2e" users.
server/internal/usecase/interactor/storytelling_test.go Adjusted expected JSON structure in story import tests to include the new GA fields.

Sequence Diagram(s)

sequenceDiagram
    participant T as Test
    participant S as Server Init
    participant NS as New Server (startServer)
    T->>S: Call ServerAndRepos/Server (with seeder)
    S->>NS: Invoke new initialization using disabledAuthConfig
    NS-->>S: Return HTTP Expect, Repo, Gateway
    S-->>T: Return initialized server components
Loading
sequenceDiagram
    participant C as Client
    participant G as GraphQL Resolver
    participant U as StoryTelling Use Case
    participant DB as Database
    C->>G: Send UpdateStory mutation (with enableGa, trackingId)
    G->>U: Forward update request with GA fields
    U->>DB: Update Story record with new GA settings
    DB-->>U: Return updated Story data
    U-->>G: Pass updated data
    G-->>C: Respond with updated Story
Loading

Possibly related PRs

Suggested labels

web

Suggested reviewers

  • pyshx
  • soneda-yuya
  • mkumbobeaty

Poem

I’m a bunny in the code garden bright,
Hopping o’er changes with pure delight.
Server and schema now dance in rhyme,
Analytics bloom—tracking every time.
With every hop, our tests sing true,
A joyful leap in code just for you! 🐇💕
Happy coding in our burrow of light!

✨ Finishing Touches
  • 📝 Generate Docstrings

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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 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.

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.

Copy link

netlify bot commented Feb 14, 2025

Deploy Preview for reearth-web ready!

Name Link
🔨 Latest commit d6707d8
🔍 Latest deploy log https://app.netlify.com/sites/reearth-web/deploys/67c024a60e39f30008e7d986
😎 Deploy Preview https://deploy-preview-1425--reearth-web.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🔭 Outside diff range comments (4)
server/internal/adapter/gql/resolver_mutation_project.go (1)

212-345: 🛠️ Refactor suggestion

Check for silent error handling and potential edge cases.

  1. At line 214: converting TeamID with _ := accountdomain.WorkspaceIDFrom(...) discards any error, risking silent failures for invalid workspace IDs.
  2. At line 223: ignoring the error on unmarshalProject.
  3. Multiple sections assume successful type assertions (e.g., prj.ImageURL() != nil).
  4. Potential incomplete rollback if partial imports fail in the middle.

Consider properly handling or surfacing all possible errors to prevent corrupted imports.

server/pkg/layer/encoding/exporter.go (1)

37-42: ⚠️ Potential issue

Critical: Encode method returns nil without performing any operations.

The Encode method has been simplified to return nil without performing any encoding operations. This could lead to silent failures in dependent systems expecting encoded data.

Please either:

  1. Document why encoding is no longer needed
  2. Implement the new encoding logic
  3. Add a deprecation notice if this is intentional
server/internal/usecase/repo/asset.go (1)

18-26: 💡 Verification agent

🧩 Analysis chain

Breaking change: Asset interface updated to include project context.

The FindByWorkspaceProject method replaces FindByWorkspace, adding project ID as a required parameter. This is a breaking change that requires updates in all implementations.


🏁 Script executed:

#!/bin/bash
# Description: Check for implementations that need updating

# Search for Asset interface implementations
ast-grep --pattern 'type $_ struct {
  $$$
}

func ($_ $_) FindByWorkspace'

# Search for existing calls to FindByWorkspace
rg "FindByWorkspace\(" --type go

Length of output: 3240


Action Required: Update all Asset interface implementations and callers to use FindByWorkspaceProject.

The Asset interface in server/internal/usecase/repo/asset.go (lines 18–26) now defines the method:

FindByWorkspaceProject(context.Context, accountdomain.WorkspaceID, *id.ProjectID, AssetFilter) ([]*asset.Asset, *usecasex.PageInfo, error)

This replaces the old FindByWorkspace method by requiring an additional project identifier. Our verification found that several callers in the codebase (e.g., in server/internal/adapter/gql/loader_asset.go and server/internal/adapter/gql/resolver_team.go, among others) are still invoking FindByWorkspace. This breaking change means that all existing implementations and calls to FindByWorkspace must be updated to handle the new project context parameter to maintain compatibility.

  • Files requiring update:
    • server/internal/adapter/gql/loader_asset.go
    • server/internal/adapter/gql/resolver_team.go
    • (Other usages in the repository should be reviewed similarly)

Please refactor these implementations and invocations accordingly.

server/internal/adapter/gql/resolver_team.go (1)

27-34: ⚠️ Potential issue

Fix method name mismatch.

The method call on line 28 uses FindByWorkspace, but the interface has been renamed to FindByWorkspaceProject.

Apply this diff to fix the method name:

-	return loaders(ctx).Asset.FindByWorkspace(ctx, obj.ID, projectID, nil, nil, &gqlmodel.Pagination{
+	return loaders(ctx).Asset.FindByWorkspaceProject(ctx, obj.ID, projectID, nil, nil, &gqlmodel.Pagination{
🧹 Nitpick comments (30)
server/internal/usecase/interfaces/project.go (1)

46-47: Consider reusing existing GA implementation.

The UpdateProjectParam struct already has GA-related fields (EnableGa and TrackingID). Consider reusing this implementation pattern for the Story component to maintain consistency.

server/e2e/gql_asset_test.go (2)

124-179: Meaningful project association tests.

The TestAssociateProjectGetAssets function thoroughly checks project-based asset creation, retrieval, reassignment, and removal. This adds valuable coverage for multi-project scenarios. If time permits, consider also testing invalid or unprivileged project moves.


200-223: Graceful error handling in createAsset.

This helper properly open/closes the file and sets up a multipart request. Consider adding an optional check or user-facing error message if the file is too large or missing, but otherwise this is good test support code.

server/internal/adapter/gql/resolver_mutation_project.go (1)

178-183: Remove or restore commented-out code.

This commented block for converting a secondary project ID is unused. Either delete it to keep the codebase clean or explain why it remains.

-	// pid2, err := gqlmodel.ToID[id.Project](gqlmodel.ID(prj.ID().String()))
-	// if err != nil {
-	// 	return nil, err
-	// }
server/e2e/gql_project_export_import_test.go (3)

19-19: Helpful test instructions.

Comment clarifies how to run tests specifically for TestProjectExportImport, potentially beneficial to new contributors.


21-63: Test flow covers exporting and importing thoroughly.

  1. Exports project → Imports project → Checks resulting structure.
  2. Removes temporary file on completion (line 58).
    Consider adding a test for invalid or missing files to ensure graceful error handling.

172-186: Scene retrieval can fail silently.

getScene expects a valid scene node in the response. If the node is missing or an error occurs, the test might be misled. Consider adding explicit checks for GraphQL errors or null nodes.

server/e2e/seeder.go (8)

47-47: Project description auto-derived from name.

pDesc = pName + " desc" is a simple pattern. If more dynamic or localized descriptions are needed in the future, consider a more robust approach.


51-55: Global IDs for layers and storytelling.

Defining global IDs is convenient for e2e tests. Ensure they won't conflict with other tests or seeders if parallel testing is introduced.


73-86: Language-specific seeding logic.

baseSeederWithLang parallels baseSeeder but adds a user language preference. This approach is consistent, though you might unify them to reduce duplication if needed.


169-183: Full seeder duplicates setup steps.

fullSeeder redoes part of baseSeeder. Evaluate merging them or calling baseSeeder first to keep code DRY.


283-312: Story and page creation logic.

Allows test environment to have a story with a single page. This is straightforward. Consider test coverage for multiple pages, blocks, and page transitions if needed.


314-338: Page creation with a single story block.

Similar to the story logic, it ensures minimal coverage. Expand for multi-block scenarios if thorough coverage is desired.


370-393: Layer style creation injection.

The style JSON is partially hardcoded. For more flexible tests, consider parameterizing or randomizing style data.


395-448: Adding a simple NLS layer with minimal config.

Uses partial JSON for testing a layer’s environment. Check potential for testing advanced or more complex configurations to broaden coverage.

server/e2e/gql_storytelling_test.go (3)

15-55: Solid test coverage for basic story CRUD.

Adding a check for the updated story title after updateStory might further strengthen this test's coverage.


208-229: Property value updates are covered.

Consider adding tests for edge cases (e.g., invalid property types) to ensure robust coverage.


231-413: Thorough publishing test.

You might add a scenario testing a story with enableGa set to true to confirm GA fields appear in the published output.

server/pkg/asset/asset.go (1)

34-40: Consider adding validation in SetProject method.

While the getter and setter methods are implemented correctly, consider adding validation in SetProject to ensure data integrity.

 func (a *Asset) SetProject(project *ProjectID) {
+    if project != nil {
+        if err := project.Validate(); err != nil {
+            return
+        }
+    }
     a.project = project
 }
server/internal/infrastructure/mongo/mongodoc/asset.go (1)

36-40: LGTM! Consider consolidating string conversion logic.

The project ID conversion logic is well-implemented with proper error handling. However, consider extracting the string conversion logic into a helper function to avoid duplication.

Consider creating a helper function:

func projectIDToString(id *idx.ID[id.Project]) *string {
    if id == nil {
        return nil
    }
    s := id.String()
    return &s
}

Also applies to: 65-72

server/internal/adapter/gql/resolver_mutation_asset.go (1)

41-62: Consider standardizing error variable names.

The implementation looks good, but there's an inconsistency in error variable naming (err vs err2).

-	a, p, err2 := usecases(ctx).Asset.Update(ctx, aid, pid, getOperator(ctx))
-	if err2 != nil {
-		return nil, err2
+	a, p, err := usecases(ctx).Asset.Update(ctx, aid, pid, getOperator(ctx))
+	if err != nil {
+		return nil, err
server/pkg/storytelling/story_bulider.go (1)

129-132: Consider adding validation for tracking ID.

The tracking ID should be validated to ensure it follows the Google Analytics format (e.g., 'UA-XXXXX-Y' or 'G-XXXXXXXX').

 func (b *StoryBuilder) TrackingID(trackingID string) *StoryBuilder {
+	if trackingID == "" {
+		return b
+	}
+	// Validate GA4 format (G-XXXXXXXX) or Universal Analytics format (UA-XXXXX-Y)
+	if !regexp.MustCompile(`^(G-[A-Z0-9]+|UA-\d+-\d+)$`).MatchString(trackingID) {
+		return b
+	}
 	b.s.trackingID = trackingID
 	return b
 }
server/internal/adapter/gql/gqlmodel/convert_project.go (1)

127-137: Consider enhancing error handling in JSON conversion.

The current implementation silently returns nil on JSON conversion errors. Consider:

  1. Logging errors for debugging
  2. Returning errors to the caller for better error handling
 func ToProjectExportFromJSON(data map[string]any) *ProjectExport {
 	var p ProjectExport
 	bytes, err := json.MarshalIndent(data, "", "  ")
 	if err != nil {
+		log.Printf("Failed to marshal project data: %v", err)
 		return nil
 	}
 	if err := json.Unmarshal(bytes, &p); err != nil {
+		log.Printf("Failed to unmarshal project data: %v", err)
 		return nil
 	}
 	return &p
 }
server/internal/infrastructure/mongo/asset.go (1)

96-100: Consider simplifying the URL filter logic.

The URL filter construction could be simplified by directly assigning to the filter map, similar to how other filters are handled.

-	if andFilter, ok := mongox.And(filter, "url", bson.M{
-		"$regex": primitive.Regex{Pattern: bucketPattern, Options: "i"},
-	}).(bson.M); ok {
-		filter = andFilter
-	}
+	filter["url"] = bson.M{"$regex": primitive.Regex{Pattern: bucketPattern, Options: "i"}}
server/e2e/common.go (2)

45-45: Consider using a builder pattern for repository initialization.

The repository initialization code could be more maintainable using a builder pattern.

Example refactor:

-func initRepos(t *testing.T, useMongo bool, seeder Seeder) (repos *repo.Container, file gateway.File) {
+type repoBuilder struct {
+    t *testing.T
+    useMongo bool
+    seeder Seeder
+}
+
+func newRepoBuilder(t *testing.T) *repoBuilder {
+    return &repoBuilder{t: t}
+}
+
+func (b *repoBuilder) withMongo() *repoBuilder {
+    b.useMongo = true
+    return b
+}
+
+func (b *repoBuilder) withSeeder(s Seeder) *repoBuilder {
+    b.seeder = s
+    return b
+}
+
+func (b *repoBuilder) build() (repos *repo.Container, file gateway.File) {
     ctx := context.Background()
-    if useMongo {
+    if b.useMongo {
         // ... rest of the code
     }
}

Also applies to: 51-71


73-82: Consider adding error handling for file initialization.

The file initialization in initGateway could benefit from explicit error handling.

Example refactor:

 func initGateway() *gateway.Container {
     if fr == nil {
+        file, err := fs.NewFile(afero.NewMemMapFs(), "https://example.com/")
+        if err != nil {
+            panic(fmt.Sprintf("failed to initialize file gateway: %v", err))
+        }
         return &gateway.Container{
-            File: lo.Must(fs.NewFile(afero.NewMemMapFs(), "https://example.com/")),
+            File: file,
         }
     }
     return &gateway.Container{
         File: *fr,
     }
 }
server/internal/usecase/interactor/project.go (1)

627-649: Improve error handling in AddZipAsset.

The AddZipAsset function should provide more specific error messages and consider using structured error types.

Example refactor:

 func AddZipAsset(ctx context.Context, file gateway.File, zipWriter *zip.Writer, path string) error {
     fileName := strings.TrimPrefix(path, "/assets/")
     stream, err := file.ReadAsset(ctx, fileName)
     if err != nil {
-        return nil // skip if external URL
+        // Skip if external URL, but log for debugging
+        fmt.Printf("Skipping external URL: %s, err: %v\n", path, err)
+        return nil
     }
     defer func() {
         if cerr := stream.Close(); cerr != nil {
-            fmt.Printf("Error closing file: %v\n", cerr)
+            fmt.Printf("Error closing file %s: %v\n", fileName, cerr)
         }
     }()
     zipEntryPath := fmt.Sprintf("assets/%s", fileName)
     zipEntry, err := zipWriter.Create(zipEntryPath)
     if err != nil {
-        return err
+        return fmt.Errorf("failed to create zip entry for %s: %w", fileName, err)
     }
     _, err = io.Copy(zipEntry, stream)
     if err != nil {
         _ = stream.Close()
-        return err
+        return fmt.Errorf("failed to copy asset %s to zip: %w", fileName, err)
     }
     return nil
 }
server/internal/usecase/interactor/nlslayer.go (1)

1109-1134: Consider using a batch operation for feature creation.

The current implementation creates features one by one in a loop. For large feature collections, this could be inefficient.

Consider refactoring to use a batch operation:

-features := make([]nlslayer.Feature, 0)
-for _, featureJSON := range nlsLayerJSON.SketchInfo.FeatureCollection.Features {
+features := make([]nlslayer.Feature, 0, len(nlsLayerJSON.SketchInfo.FeatureCollection.Features))
+for _, featureJSON := range nlsLayerJSON.SketchInfo.FeatureCollection.Features {
server/gql/asset.graphql (1)

5-5: Consider making projectId non-nullable for better data consistency.

The projectId field is currently optional. If assets should always be associated with a project, consider making this field required.

-  projectId: ID
+  projectId: ID!

Also applies to: 24-24

server/Makefile (1)

85-91: Consider making the container name configurable and add error handling.

While the new target is useful, there are a few improvements that could make it more robust:

  1. The container name is hardcoded which could break if the project name changes.
  2. No error handling if the container stop fails.

Consider this improvement:

+MONGO_CONTAINER ?= reearth-visualizer-reearth-mongo-1
 run-reset:
-	docker stop reearth-visualizer-reearth-mongo-1
+	docker stop $(MONGO_CONTAINER) || true
 	rm -rf ../mongo
 	rm -rf data
 	make run-db
 	make mockuser
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ad2fc9 and 7c10081.

⛔ Files ignored due to path filters (4)
  • server/e2e/test.zip is excluded by !**/*.zip
  • web/src/services/gql/__gen__/fragmentMatcher.json is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/gql.ts is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/graphql.ts is excluded by !**/__gen__/**
📒 Files selected for processing (107)
  • server/Makefile (3 hunks)
  • server/e2e/common.go (7 hunks)
  • server/e2e/dataset_export_test.go (0 hunks)
  • server/e2e/gql_asset_test.go (6 hunks)
  • server/e2e/gql_custom_property_test.go (1 hunks)
  • server/e2e/gql_layer_test.go (0 hunks)
  • server/e2e/gql_me_test.go (1 hunks)
  • server/e2e/gql_nlslayer_test.go (0 hunks)
  • server/e2e/gql_project_export_import_test.go (3 hunks)
  • server/e2e/gql_project_export_test.go (0 hunks)
  • server/e2e/gql_project_test.go (6 hunks)
  • server/e2e/gql_scene_test.go (4 hunks)
  • server/e2e/gql_storytelling_test.go (6 hunks)
  • server/e2e/gql_user_test.go (2 hunks)
  • server/e2e/gql_validate_geojson_test.go (1 hunks)
  • server/e2e/mock_test.go (1 hunks)
  • server/e2e/ping_test.go (1 hunks)
  • server/e2e/seeder.go (5 hunks)
  • server/go.mod (1 hunks)
  • server/gql/asset.graphql (5 hunks)
  • server/gql/cluster.graphql (0 hunks)
  • server/gql/dataset.graphql (0 hunks)
  • server/gql/layer.graphql (0 hunks)
  • server/gql/plugin.graphql (2 hunks)
  • server/gql/property.graphql (0 hunks)
  • server/gql/scene.graphql (1 hunks)
  • server/gql/storytelling.graphql (3 hunks)
  • server/gql/tag.graphql (0 hunks)
  • server/gql/workspace.graphql (2 hunks)
  • server/gqlgen.yml (0 hunks)
  • server/internal/adapter/gql/context.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/dataloader.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_layer.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go (5 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project.go (2 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_scene.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_tag.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (9 hunks)
  • server/internal/adapter/gql/loader.go (0 hunks)
  • server/internal/adapter/gql/loader_asset.go (2 hunks)
  • server/internal/adapter/gql/loader_dataset.go (0 hunks)
  • server/internal/adapter/gql/loader_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset_schema.go (0 hunks)
  • server/internal/adapter/gql/resolver_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_asset.go (3 hunks)
  • server/internal/adapter/gql/resolver_mutation_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (6 hunks)
  • server/internal/adapter/gql/resolver_mutation_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_storytelling.go (1 hunks)
  • server/internal/adapter/gql/resolver_mutation_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_property.go (0 hunks)
  • server/internal/adapter/gql/resolver_property_test.go (0 hunks)
  • server/internal/adapter/gql/resolver_query.go (1 hunks)
  • server/internal/adapter/gql/resolver_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_team.go (1 hunks)
  • server/internal/app/app.go (2 hunks)
  • server/internal/app/auth_client.go (1 hunks)
  • server/internal/infrastructure/fs/file.go (2 hunks)
  • server/internal/infrastructure/fs/file_test.go (3 hunks)
  • server/internal/infrastructure/memory/asset.go (1 hunks)
  • server/internal/infrastructure/mongo/asset.go (2 hunks)
  • server/internal/infrastructure/mongo/mongodoc/asset.go (3 hunks)
  • server/internal/infrastructure/mongo/mongodoc/scene.go (0 hunks)
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go (4 hunks)
  • server/internal/usecase/interactor/asset.go (6 hunks)
  • server/internal/usecase/interactor/layer.go (0 hunks)
  • server/internal/usecase/interactor/nlslayer.go (3 hunks)
  • server/internal/usecase/interactor/project.go (5 hunks)
  • server/internal/usecase/interactor/project_test.go (0 hunks)
  • server/internal/usecase/interactor/scene.go (5 hunks)
  • server/internal/usecase/interactor/scene_test.go (0 hunks)
  • server/internal/usecase/interactor/storytelling.go (1 hunks)
  • server/internal/usecase/interfaces/asset.go (2 hunks)
  • server/internal/usecase/interfaces/project.go (2 hunks)
  • server/internal/usecase/interfaces/story.go (1 hunks)
  • server/internal/usecase/repo/asset.go (1 hunks)
  • server/pkg/asset/asset.go (2 hunks)
  • server/pkg/asset/builder.go (1 hunks)
  • server/pkg/asset/id.go (1 hunks)
  • server/pkg/layer/encoding/exporter.go (1 hunks)
  • server/pkg/layer/layerops/processor_test.go (0 hunks)
  • server/pkg/nlslayer/feature.go (0 hunks)
  • server/pkg/nlslayer/feature_test.go (1 hunks)
  • server/pkg/scene/builder/builder.go (1 hunks)
  • server/pkg/scene/builder/builder_test.go (0 hunks)
  • server/pkg/scene/builder/encoder.go (1 hunks)
  • server/pkg/scene/builder/encoder_test.go (0 hunks)
  • server/pkg/scene/builder/scene.go (1 hunks)
  • server/pkg/scene/builder/story.go (1 hunks)
  • server/pkg/scene/builder_test.go (0 hunks)
  • server/pkg/storytelling/story.go (4 hunks)
  • server/pkg/storytelling/story_bulider.go (1 hunks)
  • web/src/services/api/propertyApi/utils.ts (1 hunks)
⛔ Files not processed due to max files limit (6)
  • web/src/services/api/sceneApi.ts
  • web/src/services/gql/fragments/dataset.ts
  • web/src/services/gql/fragments/index.ts
  • web/src/services/gql/fragments/layer.ts
  • web/src/services/gql/fragments/property.ts
  • web/src/services/gql/queries/scene.ts
💤 Files with no reviewable changes (47)
  • server/pkg/nlslayer/feature.go
  • server/internal/usecase/interactor/scene_test.go
  • server/internal/adapter/gql/context.go
  • server/internal/adapter/gql/gqlmodel/models.go
  • server/pkg/scene/builder/builder_test.go
  • server/internal/adapter/gql/gqlmodel/convert.go
  • server/e2e/gql_nlslayer_test.go
  • server/internal/usecase/interactor/project_test.go
  • server/internal/infrastructure/mongo/mongodoc/scene.go
  • server/internal/adapter/gql/resolver_property_test.go
  • server/internal/adapter/gql/gqlmodel/convert_tag.go
  • server/gqlgen.yml
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go
  • server/e2e/gql_project_export_test.go
  • server/pkg/layer/layerops/processor_test.go
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go
  • server/internal/adapter/gql/gqlmodel/convert_scene.go
  • server/e2e/gql_layer_test.go
  • server/pkg/scene/builder/encoder_test.go
  • server/e2e/dataset_export_test.go
  • server/gql/cluster.graphql
  • server/internal/usecase/interactor/layer.go
  • server/pkg/scene/builder_test.go
  • server/gql/property.graphql
  • server/gql/dataset.graphql
  • server/internal/adapter/gql/gqldataloader/dataloader.go
  • server/internal/adapter/gql/gqlmodel/convert_layer.go
  • server/internal/adapter/gql/loader.go
  • server/internal/adapter/gql/resolver_mutation_scene.go
  • server/internal/adapter/gql/resolver_scene.go
  • server/internal/adapter/gql/resolver_dataset.go
  • server/internal/adapter/gql/resolver_layer.go
  • server/internal/adapter/gql/resolver_mutation_tag.go
  • server/internal/adapter/gql/resolver_property.go
  • server/internal/adapter/gql/resolver_dataset_schema.go
  • server/internal/adapter/gql/resolver_tag.go
  • server/gql/tag.graphql
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go
  • server/internal/adapter/gql/loader_dataset.go
  • server/gql/layer.graphql
  • server/internal/adapter/gql/resolver_mutation_layer.go
  • server/internal/adapter/gql/loader_tag.go
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go
  • server/internal/adapter/gql/resolver_mutation_dataset.go
✅ Files skipped from review due to trivial changes (2)
  • server/pkg/scene/builder/encoder.go
  • server/go.mod
🧰 Additional context used
🧠 Learnings (2)
server/internal/adapter/gql/resolver_mutation_project.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:215-217
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project import functionality, the file names are unique and can be safely replaced uniformly using `bytes.Replace`.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:0-0
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project export functionality, the ZIP file is deleted after saving to storage.
server/e2e/gql_project_export_import_test.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:48-80
Timestamp: 2024-11-12T15:21:04.151Z
Learning: In `server/e2e/gql_import_export_test.go`, test functions like `TestCallImportProject` may have disabled assertions due to problems with GraphQL.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:12-44
Timestamp: 2024-11-12T15:21:04.150Z
Learning: The response body assertions in `TestCallExportProject` are disabled due to existing problems with GraphQL.
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: ci-server / ci-server-test
  • GitHub Check: Redirect rules - reearth-web
  • GitHub Check: Header rules - reearth-web
  • GitHub Check: Pages changed - reearth-web
🔇 Additional comments (114)
server/internal/usecase/interfaces/project.go (2)

14-14: LGTM! Import addition improves type safety.

The addition of the idx package import supports the use of strongly-typed IDs, which is a good practice for type safety.


74-75: LGTM! Method signature changes improve clarity and type safety.

The changes enhance the interface by:

  • Using more descriptive method names with "Data" suffix
  • Replacing string with strongly-typed workspace ID
  • Adding operator parameter for proper authorization
server/pkg/scene/builder/builder.go (3)

26-38: LGTM: Clean removal of encoder-related code.

The removal of the encoder field and its initialization is consistent with the broader refactoring effort mentioned in the summary.

Also applies to: 40-57


91-126: LGTM: GA parameters correctly propagated.

The addition of Google Analytics parameters (enableGa, trackingId) is consistent with the PR objectives, and they are properly propagated through the build methods.

Also applies to: 128-163


165-182:

❓ Verification inconclusive

Verify the impact of removing layers parameter from sceneJSON.

The removal of the layers parameter from sceneJSON call aligns with the encoder removal. However, ensure that layer data is still properly handled in the sceneJSON method.


🏁 Script executed:

#!/bin/bash
# Description: Check if sceneJSON method still handles layer data correctly

# Search for sceneJSON method definition and its usage of layer data
ast-grep --pattern 'func $_(b *Builder) sceneJSON($_, $_, $_, $_, $_, $_) (*sceneJSON, error) {
  $$$
}'

Length of output: 106


Manual Verification Required:
The previous ast-grep check did not return any output, so please run the above script to manually inspect the definition of sceneJSON in server/pkg/scene/builder/builder.go. In particular, verify whether the method includes logic to process or incorporate layer data internally now that the layer parameter has been removed from the call.


🏁 Script executed:

#!/bin/bash
# Verify the definition of sceneJSON in the file and inspect if it includes any processing of layer data.
rg -n "func (b \\*Builder) sceneJSON" -A 15 server/pkg/scene/builder/builder.go

Length of output: 80


Manual Verification Required: Confirm Layer Data Handling in sceneJSON
The removal of the layers parameter in the call to sceneJSON appears to align with the removal of encoder dependencies. However, our automated checks did not yield any output to confirm whether the implementation of sceneJSON—presumably defined in server/pkg/scene/builder/builder.go or elsewhere—now correctly incorporates layer data.
• Please manually inspect the definition of sceneJSON to ensure that any necessary layer data processing is still performed internally.
• Adjust the implementation if layer data is required but no longer being explicitly passed via a parameter.

server/internal/usecase/interactor/asset.go (4)

39-44: Ensure consistent usage of project filter.

The newly introduced FindByWorkspaceProject method effectively includes pid as a filter. Verify that this function is consistently called elsewhere and that callers handle the possibility of a nil pid correctly.


154-154: New import function signature looks good.

The updated signature includes a project parameter, aligning with project-based asset management. Keep watch for potential oversights in verifying that the provided project ID is valid and that the caller has sufficient permissions.


166-169: Confirm usage of realName for the file path.

Using realName instead of the original zipFile.Name is fine if that aligns with your domain requirements. Ensure it does not unintentionally overwrite files with common names and that any required sanitization or uniqueness checks are performed.


181-182: Properly set project and asset name.

Assigning project and deriving name from path.Base(realName) is consistent and clear. This design supports flexible naming without leaking directory information.

server/internal/adapter/gql/resolver_query.go (1)

15-16: Align resolver call with new project-based asset retrieval.

The updated Assets method now accepts an optional projectId but calls FindByWorkspace(...) under the hood. Confirm that this loader indeed supports a project-based query (possibly via an internal rename or bridging). Otherwise, consider calling a method explicitly named FindByWorkspaceProject for clarity.

server/e2e/gql_asset_test.go (8)

15-16: Test label change detected.

The rename to TestCoreSupportGetAssets clarifies the purpose of testing asset retrieval with varying coreSupport flags.


17-120: Potential mismatch between test scenario and final assertions.

Within TestCoreSupportGetAssets, multiple assets with different coreSupport values are created, but at line 117 all returned assets are asserted to have coreSupport == true. Consider verifying or filtering by coreSupport in the query to avoid confusion or unintended failures.


122-122: New test directive comment.

Adding // go test -v -run TestAssociateProjectGetAssets is a helpful reminder for targeted test runs.


181-199: GraphQL mutation now accommodates project IDs.

Adding the projectId field keeps creation logic flexible. Confirm that clients passing a nil projectId won’t cause unintended consequences.
[approve]


224-241: Update mutation coverage.

updateAsset effectively tests reassigning an asset to a new project or removing its project. Confirm that negative or unauthorized scenarios are handled upstream or with additional test coverage.


242-257: Asset removal workflow.

removeAsset is straightforward, ensuring that the desired asset is removed after test usage. This helps maintain a clean setup/teardown policy in e2e tests.


258-286: Temporary file creation for asset upload testing.

createAssetFromFileData handles temporary file creation and cleanup. Great approach for varied input data testing. Just ensure large or malformed data is gracefully handled or validated.


287-351: Updated getAssets query with optional projectId.

Including projectId in the query supports fine-grained filtering in the e2e tests. This ensures consistent alignment with the new server-side project-based asset fetching logic.

server/internal/adapter/gql/resolver_mutation_project.go (9)

10-12: Imports look appropriate.

The newly added packages (net/url and path) are relevant to parse asset URLs and handle file paths. No immediate concerns.


130-130: Watch out for special characters in file names.

Using the string form of input.ProjectID directly in the zip file name could lead to malformed or unexpected filenames if the project ID contains special or unsafe characters. Consider sanitizing the string to avoid filesystem issues.


155-155: Ensure all exported data is validated thoroughly.

The call to Project.ExportProjectData is critical. Ensure validations around the project data are handled properly, especially edge cases where the project might be incomplete or contain invalid references.


189-195: Validate parsed asset filenames.

When extracting filename using path.Base(parsedURL.Path), ensure that any special or leading/trailing characters are handled. Filenames could conflict with existing assets or contain malicious patterns.


197-199: Data map population seems coherent.

Storing the project, plugins, and schemas in data is clear. Ensure these keys match the reading logic in downstream code (e.g., unmarshalProject, unmarshalPluginsScene, etc.).


333-345: Guard against invalid JSON structure.

replaceOldSceneID uses a type assertion:

oldSceneData, _ := jsonData["scene"].(map[string]interface{})
oldSceneID := oldSceneData["id"].(string)

If jsonData["scene"] or "id" isn't present or is of the wrong type, this could panic. Recommend gracefully checking.


346-354: Verify presence of “project” field in JSON.

unmarshalProject calls jsonData["project"].(map[string]interface{}) without confirming it exists or is the correct type. A missing or different type would return nil or cause an error. Add validations to handle unexpected structures.


356-370: Handle missing or malformed asset entries.

unmarshalAssets similarly converts jsonData["assets"] to map[string]interface{}; a missing key or unexpected structure can cause issues. Consider checking each asset entry type before usage.


372-382: Check for optional plugin & schema fields.

unmarshalPluginsScene calls jsonData["plugins"].([]interface{}) and jsonData["schemas"].([]interface{}). If these fields do not exist or are incorrectly typed, it may cause runtime issues.

server/e2e/gql_project_export_import_test.go (5)

4-8: Imported packages are consistent with new functionality.

All required imports for JSON handling, HTTP, etc. are properly referenced. No issues found.


12-13: ID usage is correct.

New references to id and idx fit seamlessly into the test logic.


65-101: Comparison utilities appear robust.

compareValue, convertLine, and ignoring certain fields (e.g., IDs) is a good approach. However, consider verifying that ignoring these fields won’t mask legitimate mismatches.


121-139: Export function logic is sound.

The test fetches the exported zip from the server, writes it locally, and then ensures no error. This design effectively simulates a real export scenario.


141-169: Import process is thorough.

importProject uses multipart file uploads and checks the projectData field. For completeness, consider verifying error scenarios (e.g., invalid file).

server/e2e/seeder.go (8)

5-9: Additional imports used correctly.

Rand, hex, JSON, and other packages align with new feature expansions (UUID generation, JSON manipulation, etc.). No concerns.


49-49: Mocked time set to a fixed date.

Using now = time.Date(2022, ...) is acceptable for stable test environments but remember to reset or mock carefully if needed in code beyond tests.


58-71: Base seeder logic extended with file gateway support.

baseSeeder now calls baseSetup with a new file parameter. This is beneficial for integrated asset seeding.


88-123: Asset addition in base setup.

Calls addAsset("test.png", ...) unconditionally. If test.png doesn’t exist, the seeder fails. Consider verifying or providing a default fallback to ensure consistent seeding.


232-281: Widget creation approach is sound.

  1. Creates plugin extension, property, and widget entries.
  2. Ensures references are saved in scene.
    Check concurrency or repeated calls if needed to avoid duplication.

340-368: Block creation with plugin extension.

Ensures that the extension is the correct type. This pattern is consistent with the plugin-based architecture.


450-510: GeoJSON injection is well structured.

The function addGeoJson manually parses geometry and updates the layer’s sketch. This approach is fine for testing. For production or more complex E2E, consider validating geometry from external or random sources.


512-527: UUID generation is straightforward.

generateUUID is a standard approach for test IDs. No concerns.

server/e2e/gql_storytelling_test.go (3)

57-152: Comprehensive page CRUD testing.

Test covers creation, duplication, ordering, and deletion of pages thoroughly.


154-206: Good coverage of block CRUD.

The test scenario ensures blocks can be created, ordered, and removed without issues.


869-901: Simplified removeBlock function.

Ensures the removed block no longer appears in the page, aligning well with expected behavior.

server/internal/adapter/gql/gqlmodel/models_gen.go (11)

138-138: ProjectID field for Asset is a good addition.

Helps tie assets to specific projects, enabling better organization.


185-186: Optional projectId in CreateAssetInput is consistent.

Allows assets to be tied to a project at creation time without breaking older workflows.


581-588: Enhanced MergedPropertyField.

Ensure all references (SchemaID, FieldID) are consistently populated to avoid merge-time null pointers.


906-915: Changed ID type from ID to string in PropertyField.

Confirm this change is intentional. It may cause type mismatches if the rest of the code expects an ID type.


919-921: Consistent linking fields in PropertyFieldLink.

Optional datasetId plus mandatory datasetSchema references look structurally sound.


1135-1149: Scene struct expansions.

Referencing project, team, and property directly promotes clarity and reduces fragmentation.


1221-1222: GA fields in Story model.

Straightforward extension for analytics tracking.


1332-1334: UpdateAssetInput now includes projectId.

Keeps input aligned with asset creation logic regarding project association.


1337-1339: UpdateAssetPayload reflects projectId assignment.

Maintains consistency with the input changes.


1459-1460: GA fields in UpdateStoryInput.

Allows toggling and setting GA tracking configuration for an existing story.


1848-1848: Extended plugin extension types.

Ensures coverage for the newly introduced story/infobox extension points.

server/e2e/gql_me_test.go (1)

8-8: Simplified server initialization.

By removing explicit config, the test now relies on default or embedded settings, reducing potential config drift.

server/e2e/ping_test.go (1)

9-9:

❓ Verification inconclusive

Verify test coverage with simplified server initialization.

The server initialization has been simplified to use ServerPingTest. While this makes the test more concise, ensure that the new initialization maintains the same test coverage for CORS and authentication behaviors that were previously explicitly configured.


🏁 Script executed:

#!/bin/bash
# Description: Check if ServerPingTest maintains the same configuration as before

# Search for ServerPingTest implementation
rg -A 10 "func ServerPingTest"

# Check for CORS and auth configuration in the implementation
rg -l "Origins.*example.com" "server/e2e"
rg -l "AuthSrv" "server/e2e"

Length of output: 692


Confirm CORS and Authentication Coverage in Simplified Server Initialization

The ServerPingTest function in server/e2e/common.go now initializes the server using disabledAuthConfig, which does not explicitly configure CORS or authentication. In contrast, the previous explicit settings (as seen in ServerMockTest) include values for Origins (e.g., "https://example.com") and AuthSrv. Please verify that tests for CORS and authentication are still exercised—either via separate tests (like those using ServerMockTest) or by extending the current test suite—to maintain comprehensive coverage.

server/pkg/layer/encoding/exporter.go (1)

10-13:

❓ Verification inconclusive

Verify removal of encoding functionality.

The Encoder field has been removed from the Exporter struct. This appears to be a breaking change that could affect layer export functionality.


🏁 Script executed:

#!/bin/bash
# Description: Check for potential impacts of removing the Encoder

# Search for usages of Exporter.Encode
rg -A 5 "Exporter.*Encode"

# Search for potential alternatives to encoding
rg -l "type.*Encoder" "server/pkg/layer"

Length of output: 594


Confirm Exporter.Encode Behavior and Downstream Impact

I verified that the Encoder field has indeed been removed from the Exporter struct in server/pkg/layer/encoding/exporter.go. The current implementation of the Encode method simply checks for a nil receiver and returns nil, effectively making it a no-op. Meanwhile, encoder implementations still exist in files such as czml.go, encoder.go, shp.go, kml.go, and geojson.go.

  • Verify that no external components rely on the previous encoding functionality provided via the Encoder field.
  • Confirm that the no-op Encode method does not cause unexpected behavior in layer export functionality.
  • If the removal is intentional as part of a decoupling refactor, please update documentation and tests accordingly.
server/pkg/asset/id.go (1)

10-26: LGTM: Well-structured additions for project ID support.

The new type alias and variables for ProjectID follow the existing patterns and maintain consistency with the codebase.

server/pkg/nlslayer/feature_test.go (1)

13-17: LGTM! Test updated to use explicit feature ID creation.

The test has been correctly updated to use NewFeature with an explicitly created feature ID, which is a better approach as it makes the ID creation more visible and controllable.

server/e2e/mock_test.go (1)

11-11: LGTM! Test setup simplified using helper function.

The change improves test maintainability by using ServerMockTest helper function instead of explicit server configuration.

server/internal/adapter/gql/gqlmodel/convert_asset.go (1)

12-16: LGTM! Proper handling of optional project ID.

The code correctly handles the optional project ID by:

  1. Using a pointer type for optional field
  2. Safely checking for nil project before dereferencing
  3. Creating a new variable to store the ID value before taking its address
server/pkg/asset/asset.go (1)

18-18: LGTM! Project field added to Asset struct.

The optional project field is correctly defined as a pointer type.

server/internal/usecase/interfaces/asset.go (2)

26-31: LGTM! Well-structured parameter type.

The addition of ProjectID to CreateAssetParam is well-typed and maintains backward compatibility by making it optional.


37-44:

✅ Verification successful

Verify implementations of the interface changes.

The interface has undergone significant changes:

  1. Renamed FindByWorkspace to FindByWorkspaceProject
  2. Added new Update method
  3. Renamed UploadAssetFile to ImportAssetFiles

🏁 Script executed:

#!/bin/bash
# Find all implementations of the Asset interface
ast-grep --pattern 'type $_ struct {
  $$$
}

func ($_ $_) FindByWorkspaceProject($$$) {
  $$$
}'

# Find any remaining old method names that need updating
rg "FindByWorkspace|UploadAssetFile" --type go

Length of output: 5035


Asset Interface Changes Verified

The recent modifications to the asset interface have been confirmed:

  • The new method signature for FindByWorkspaceProject is correctly implemented in both the memory and Mongo adapters.
  • No references to the outdated method UploadAssetFile remain, indicating that the renaming to ImportAssetFiles has been successfully applied.
  • The introduction of the new Update method appears to be integrated as expected.

All implementations are consistent with the intended interface changes.

server/pkg/asset/builder.go (1)

57-60: LGTM! Clean builder method implementation.

The new Project method follows the builder pattern consistently and maintains the fluent interface design.

server/internal/infrastructure/mongo/mongodoc/asset.go (1)

1-1: Verify PR objectives alignment with code changes.

The PR objectives mention integrating Google Analytics parameters into the Story component, but the actual changes implement project association for assets. This suggests a potential mismatch between the PR description and the implemented changes.

Please clarify if this PR's scope has changed or if these changes are prerequisites for the Google Analytics integration.

server/internal/adapter/gql/resolver_mutation_asset.go (1)

19-26: LGTM! Well-structured project ID handling.

The implementation correctly handles the optional project ID with proper error checking and type conversion.

server/pkg/scene/builder/story.go (1)

45-45: LGTM! Added missing Title field.

The change correctly adds the Title field to the JSON output, maintaining consistency with the struct definition.

server/pkg/storytelling/story_bulider.go (1)

124-127: LGTM! Clean implementation of EnableGa.

The method follows the builder pattern correctly and maintains immutability.

server/internal/adapter/gql/loader_asset.go (2)

45-52: LGTM! Well-implemented project ID handling.

The implementation correctly handles the optional project ID with proper error checking and type conversion.


60-66: LGTM! Performance optimization.

Good optimization by pre-allocating the edges array and using index-based assignment instead of append.

server/internal/infrastructure/memory/asset.go (1)

49-59: LGTM! The project filtering logic is well-implemented.

The implementation correctly handles both workspace and project-based filtering, with appropriate null checks for the project ID parameter.

server/e2e/gql_scene_test.go (2)

16-16: LGTM! Response object access has been standardized.

The changes consistently use Object().Value() for accessing response properties, improving code clarity and maintainability.

Also applies to: 42-42, 71-73


77-100: LGTM! Well-structured project creation helper.

The createProjectWithExternalImage function is well-implemented with:

  • Clear GraphQL mutation structure
  • Comprehensive project parameters
  • Proper error handling through the Request helper
server/internal/adapter/gql/gqlmodel/convert_project.go (1)

108-113: LGTM! Well-structured export type.

The ProjectExport struct is well-defined with appropriate JSON tags and field types.

server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1)

35-36: LGTM! Google Analytics fields properly integrated.

The GA fields are correctly added to the Story struct, aligning with the PR objectives for storytelling GA support.

server/e2e/gql_validate_geojson_test.go (1)

186-186: LGTM!

The addition of the nil parameter for projectId aligns with the updated function signature in the asset management system.

server/internal/usecase/interfaces/story.go (1)

38-39: LGTM!

The addition of EnableGa and TrackingID fields as pointers follows Go best practices for optional fields and aligns with the PR objective to integrate Google Analytics parameters.

server/pkg/scene/builder/scene.go (1)

27-27: LGTM!

The simplified function signature improves code clarity by removing unused parameters while maintaining the core scene building functionality.

server/internal/infrastructure/mongo/asset.go (2)

67-67: LGTM!

The function rename and signature update clearly indicate its enhanced capability to filter assets by both workspace and project.


72-80: LGTM!

The filter construction is clear and handles both project-specific and workspace-wide queries appropriately. The conditional logic ensures proper filtering based on the presence of a project ID.

server/internal/app/auth_client.go (1)

105-107: LGTM! Improved logging by skipping e2e test user.

The changes appropriately reduce log noise by skipping logging for the e2e test user while maintaining useful debug information for other users.

Also applies to: 115-117

server/e2e/gql_user_test.go (1)

28-28:

✅ Verification successful

Verify the usage of the new gateway.File parameter.

The function signature has been updated to include a gateway.File parameter, but it's not being used in the function body. Please confirm if this is intentional or if additional implementation is needed.


🏁 Script executed:

#!/bin/bash
# Description: Check if the gateway.File parameter is used in other test files.

# Search for similar function signatures
rg -A 5 'func.*gateway\.File\)'

# Search for any usage of gateway.File
rg 'gateway\.File'

Length of output: 5971


Subject: Confirmed Intentional Omission of gateway.File Usage

After reviewing the repository, it’s clear that the new gateway.File parameter is consistently added to seeder function signatures (e.g. in server/e2e/seeder.go and defined as part of the common Seeder type in server/e2e/common.go). In functions like baseSeederUser in server/e2e/gql_user_test.go, the parameter isn’t utilized in the body, which matches the established pattern across similar functions. This suggests the parameter is provided to conform to the interface requirements and may be reserved for future use rather than an oversight.

No changes are needed unless there’s an intention to leverage the parameter within the function body.

server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go (1)

11-13: LGTM! Consistent refactoring of feature creation.

The changes consistently replace NewFeatureWithNewId with NewFeature across all feature creations, maintaining the same functionality while using explicit ID generation.

Also applies to: 21-23, 31-33, 41-43, 51-53

server/pkg/storytelling/story.go (3)

41-42: LGTM! Added Google Analytics fields.

The new fields enableGa and trackingID are appropriately added to support Google Analytics integration.


134-140: LGTM! Added getter and setter methods for Google Analytics fields.

The new methods follow the codebase's naming conventions and provide clean access to the Google Analytics fields.

Also applies to: 190-196


58-60: LGTM! Added nil check to Pages() method.

The added nil check improves robustness by preventing potential nil pointer dereference.

server/internal/infrastructure/mongo/mongodoc/storytelling.go (3)

34-35: LGTM! Google Analytics fields added.

The new fields EnableGa and TrackingID have been correctly added to the StorytellingDocument struct, aligning with the PR objectives to integrate Google Analytics parameters.


87-88: LGTM! Google Analytics fields initialized.

The new fields are properly initialized in the NewStorytelling function by retrieving their values from the storytelling.Story object.


197-198: LGTM! Google Analytics fields included in model.

The new fields are correctly included when constructing a storytelling.Story object in the Model method.

server/internal/app/app.go (2)

72-78: LGTM! Authentication handling improved.

The changes improve the authentication handling by:

  1. Reinstating authConfig retrieval and logging.
  2. Adding a condition to check if authentication is disabled.
  3. Using appropriate middleware based on the authentication state.

164-168:

❓ Verification inconclusive

Verify the usage of dummy authentication.

The AuthMiddlewareDummy function bypasses authentication by simply passing through to the next handler. While this is useful for testing or development environments, ensure it's not used in production.

Run the following script to verify the usage of disabled authentication:


🏁 Script executed:

#!/bin/bash
# Description: Check for potential misuse of disabled authentication in production code.

# Test: Search for disabled authentication configuration. Expect: Only in test files.
rg -A 5 $'AuthSrv.Disabled = true'

# Test: Search for dummy auth usage. Expect: Only in test files.
rg -A 5 $'AuthMiddlewareDummy'

Length of output: 690


Critical: Confirm Environment-Specific Use of Dummy Authentication

The dummy authentication middleware (AuthMiddlewareDummy) bypasses all auth checks by simply passing control to the next handler. It is invoked in server/internal/app/app.go (see the assignment to wrapHandler). Please ensure that:

  • This dummy middleware is strictly gated by configuration (e.g., via an AuthSrv.Disabled flag or its equivalent) so that it is only enabled in development or testing scenarios.
  • A thorough review of the conditional code paths confirms that production deployments will always enable proper authentication.
server/internal/infrastructure/fs/file.go (2)

113-113: LGTM! Asset path validation improved.

The path validation in RemoveAsset now correctly includes assetDir in the expected directory path, ensuring assets are only removed from their designated directory.


286-286: LGTM! Asset URL structure improved.

The URL construction in getAssetFileURL now correctly includes assetDir in the path, ensuring a consistent URL structure for assets.

web/src/services/api/propertyApi/utils.ts (1)

247-248:

✅ Verification successful

Verify the removal of actualValue field.

The actualValue field has been removed from the MergedPropertyField type. While this simplifies the data structure, ensure that all consumers of this type have been updated accordingly.

Run the following script to verify the usage of actualValue:


🏁 Script executed:

#!/bin/bash
# Description: Check for any remaining references to actualValue.

# Test: Search for actualValue usage. Expect: No references in property-related code.
rg -A 5 $'actualValue'

# Test: Search for MergedPropertyField type usage. Expect: Updated type definitions.
ast-grep --pattern $'type MergedPropertyField = {
  $$$
  actualValue
  $$$
}'

Length of output: 13944


Confirmed: actualValue field successfully removed from MergedPropertyField

  • The updated code in web/src/services/api/propertyApi/utils.ts now shows:

        // "fieldId" | "actualValue" | "overridden"
        "fieldId" | "overridden"

    which indicates that the actualValue field has been removed as intended.

  • Verification via the provided commands shows no remaining references to actualValue within the type definition. While some unrelated files (e.g., tests and GraphQL fragments) still reference the identifier (or have it commented), these do not affect consumers of the MergedPropertyField type.

Please ensure that any external consumers have been updated accordingly.

server/internal/infrastructure/fs/file_test.go (3)

116-117: LGTM! Base URL change maintains test functionality.

The change from /assets to / as the base URL is correct, as the test still verifies that asset paths are properly constructed with /assets/ prefix (see assertion on line 128).


165-165: LGTM! Base URL change maintains test functionality.

The change from https://example.com/assets to https://example.com/ is correct, as the test cases still verify proper asset path handling with /assets/ prefix in URLs.


316-316: LGTM! Base URL change maintains test functionality.

The change from http://hoge.com/assets to http://hoge.com/ is correct, as the test still verifies proper asset URL construction.

server/internal/adapter/gql/resolver_mutation_storytelling.go (1)

58-59: LGTM! Google Analytics parameters properly integrated.

The addition of EnableGa and TrackingID fields to the UpdateStoryInput struct correctly implements the PR objective of adding Google Analytics support to stories.

server/e2e/common.go (2)

35-43: LGTM! Well-structured configuration variables.

The addition of fr and disabledAuthConfig variables improves code organization by centralizing configuration.


283-297: LGTM! Improved type handling in ValueDump.

The update to ValueDump properly handles different types and includes helpful error messaging.

server/internal/usecase/interactor/project.go (1)

513-513: LGTM! Improved asset handling in export functionality.

The renaming to ExportProjectData and use of AddZipAsset helper function improves code organization and reusability.

Also applies to: 526-527

server/e2e/gql_custom_property_test.go (1)

347-762: LGTM! Well-structured GraphQL query with improved organization.

The query has been enhanced with:

  • Better organization using fragments for improved readability and maintainability
  • Comprehensive coverage of all necessary fields including GA support
  • Logical grouping of related fields
server/e2e/gql_project_test.go (1)

42-42: LGTM! Test assertions updated to match expected project counts.

The test expectations have been correctly updated to reflect the current project count requirements.

Also applies to: 144-144, 585-585, 617-617

server/internal/usecase/interactor/scene.go (2)

680-683: LGTM! Improved null safety in story handling.

The code now safely handles the case where the story list is nil or empty, preventing potential nil pointer dereferences.


711-714: LGTM! Enhanced asset handling with better error logging.

The code now uses AddZipAsset with improved error handling and logging.

server/internal/usecase/interactor/storytelling.go (1)

188-194: LGTM! Added Google Analytics support to story updates.

The implementation correctly handles the new GA fields:

  • EnableGa for enabling/disabling GA
  • TrackingID for setting the GA tracking ID
server/internal/usecase/interactor/nlslayer.go (1)

859-861: LGTM! Feature creation logic has been improved.

The change to use nlslayer.NewFeatureID() for generating feature IDs is a good practice as it ensures consistent ID generation.

server/gql/scene.graphql (1)

50-50: LGTM! Improved GraphQL schema formatting.

The added space after extend type Query improves readability and follows GraphQL schema formatting conventions.

server/gql/asset.graphql (1)

29-32: LGTM! Well-structured update types.

The new UpdateAssetInput and UpdateAssetPayload types are well-defined and maintain consistency with GraphQL best practices.

Also applies to: 49-52

server/gql/plugin.graphql (1)

99-99: LGTM! Improved GraphQL schema formatting.

The formatting changes improve readability while maintaining the schema's functionality.

Also applies to: 109-109

server/gql/workspace.graphql (2)

8-14: LGTM! The addition of projectId parameter enhances asset filtering capabilities.

The new parameter allows for more granular asset queries within specific projects.


15-21: LGTM! Code formatting improvements enhance readability.

The multi-line formatting of method signatures and mutation parameters improves code readability while maintaining functionality.

Also applies to: 121-123

server/gql/storytelling.graphql (1)

75-89: LGTM! Well-organized input type with clear section comments.

The Google Analytics fields are properly added to the UpdateStoryInput type, maintaining consistency with other optional update fields. The section comments improve code organization.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (7)
server/gql/scene.graphql (1)

1-17: 💡 Verification agent

🧩 Analysis chain

Consider updating the schema to support GA in stories.

The PR objectives mention adding Google Analytics support to the Story component with enableGa and trackingId fields. Since the Scene type includes a stories field, consider updating the Story type schema to include these new GA-related fields.

Let's check if the Story type schema exists and needs to be updated:


🏁 Script executed:

#!/bin/bash
# Description: Check for Story type schema definition
rg -A 10 'type Story' server/gql/

Length of output: 2761


Update Story Schema for GA Support

The Story type defined in server/gql/storytelling.graphql does not yet include the GA-related fields described in the PR objectives. To fully support Google Analytics in stories, please update the Story schema by adding the new fields—likely enableGa (Boolean) and trackingId (String).

server/pkg/scene/builder/story.go (2)

11-18: ⚠️ Potential issue

Add missing Google Analytics fields to storyJSON struct.

According to the PR objectives, the Story component should include Google Analytics support. Please add the following mandatory fields to the struct:

  • EnableGa bool - For enabling/disabling Google Analytics
  • TrackingId string - For the Google Analytics tracking ID

Apply this diff to add the missing fields:

 type storyJSON struct {
 	ID            string       `json:"id"`
 	Title         string       `json:"title"`
 	Property      propertyJSON `json:"property"`
 	Pages         []pageJSON   `json:"pages"`
 	PanelPosition string       `json:"position"`
 	BgColor       string       `json:"bgColor"`
+	EnableGa      bool         `json:"enableGa"`
+	TrackingId    string       `json:"trackingId"`
 }

38-56: ⚠️ Potential issue

Update storyJSON function to support Google Analytics.

The function needs to be updated to accept and populate the Google Analytics fields.

Apply this diff to update the function:

-func (b *Builder) storyJSON(ctx context.Context, p []*property.Property) (*storyJSON, error) {
+func (b *Builder) storyJSON(ctx context.Context, p []*property.Property, enableGa bool, trackingId string) (*storyJSON, error) {
 	if b.story == nil {
 		return nil, nil
 	}

 	return &storyJSON{
 		ID:       b.story.Id().String(),
 		Title:    b.story.Title(),
 		Property: b.property(ctx, findProperty(p, b.story.Property())),
 		Pages: lo.FilterMap(b.story.Pages().Pages(), func(page *storytelling.Page, _ int) (pageJSON, bool) {
 			if page == nil {
 				return pageJSON{}, false
 			}
 			return b.pageJSON(ctx, *page, p), true
 		}),
 		PanelPosition: string(b.story.PanelPosition()),
 		BgColor:       b.story.BgColor(),
+		EnableGa:      enableGa,
+		TrackingId:    trackingId,
 	}, nil
 }
server/pkg/scene/builder/scene.go (1)

23-24: 🛠️ Refactor suggestion

Fix inconsistent field names and JSON tags.

The Google Analytics field names and JSON tags are inconsistent with the naming convention used in storyJSON:

  • EnableGA vs EnableGa
  • TrackingID vs TrackingId
  • enableGa vs enableGA
  • trackingId vs trackingID

Apply this diff to maintain consistency:

-	EnableGA          bool                    `json:"enableGa"`
-	TrackingID        string                  `json:"trackingId"`
+	EnableGa          bool                    `json:"enableGa"`
+	TrackingId        string                  `json:"trackingId"`
server/pkg/layer/encoding/exporter.go (1)

37-42: ⚠️ Potential issue

Critical: Encode method now returns nil without performing any encoding.

The Encode method has been modified to return nil without performing any encoding operations. This could silently break functionality that depends on proper layer encoding.

Please either:

  1. Restore the encoding functionality
  2. Document why encoding is no longer needed
  3. Update all dependent code to handle this change
server/internal/usecase/repo/asset.go (1)

18-26: 💡 Verification agent

🧩 Analysis chain

LGTM! Well-structured interface change to support project-based filtering.

The addition of FindByWorkspaceProject with project ID support is a good enhancement to the asset management system.


🏁 Script executed:

#!/bin/bash
# Verify that all Asset implementations have been updated
rg -A 5 "type\s+.*Asset\s+struct"

# Search for any remaining references to FindByWorkspace
rg "FindByWorkspace\("

Length of output: 5522


Action Required: Update All Call Sites to Use FindByWorkspaceProject

The interface change in server/internal/usecase/repo/asset.go is well-structured and enhances asset filtering by introducing project support. However, the verification revealed several remaining references to the old FindByWorkspace method. Please review and update these call sites to ensure consistency with the new interface method. For example:

  • GraphQL Adapters:

    • server/internal/adapter/gql/loader_asset.go still defines a FindByWorkspace method.
    • server/internal/adapter/gql/loader_project.go and resolver files (e.g., resolver_query.go, resolver_team.go) are invoking FindByWorkspace.
  • Usecase and Repository Layers:

    • References in server/internal/usecase/interactor/project.go and server/internal/usecase/repo/project.go still use the old method signature.

Please verify that all implementations of the Asset interface and their consumers are updated to use FindByWorkspaceProject to avoid potential integration issues.

server/go.mod (1)

192-192: 💡 Verification agent

🧩 Analysis chain

Verify Go version.

The specified Go version 1.23.5 appears to be incorrect as it's higher than the latest available Go version.


🌐 Web query:

What is the latest version of Go programming language?

💡 Result:

The latest stable release of the Go programming language as of February 2025 is Go 1.23.6, released on February 4, 2025[2][6]. This follows six months after Go 1.22 and continues Go's tradition of maintaining backward compatibility while introducing toolchain and runtime improvements[3][4].

Key details about current versions:

  • Latest minor update: 1.23.6 contains security fixes for crypto/elliptic and bug fixes to the compiler/go command[2]
  • Previous major versions still receiving security updates:
    • 1.22.x line up to v1.22.12[2]
    • 1.21.x line up to v1.21.12[2]

Go uses a unique versioning scheme where:

  • The second number indicates major releases (e.g., 1.**23**.0)
  • The third number represents patch/security updates (e.g., 1.23.**6**)
  • Maintains compatibility across all 1.x versions per the Go 1 promise[5]

The language currently uses time-based releases every 6 months, with new major versions typically arriving in August and February each year[3][4]. Developers are encouraged to update regularly while older versions remain supported for security fixes through at least two subsequent major releases[5].

Citations:


Consider updating the Go version in go.mod.
The file currently specifies:

go 1.23.5

As of February 2025, the latest stable release is Go 1.23.6—which includes important security fixes for crypto/elliptic and bug fixes for the compiler and go command. While using 1.23.5 is acceptable if intentional, please verify whether updating to 1.23.6 is desired to ensure you benefit from the most recent improvements.

🧹 Nitpick comments (29)
web/src/services/api/propertyApi/utils.ts (1)

247-248: Remove commented-out type signature.

The type signature change looks good, but please remove the commented-out old type signature as it's not providing value.

-    // "fieldId" | "actualValue" | "overridden"
     "fieldId" | "overridden"
server/pkg/scene/builder/builder.go (1)

135-135: Improve consistency of error messages.

The error messages in BuildResult could be more consistent. Consider standardizing the format:

-		return nil, errors.New("Fail buildScene :" + err.Error())
-		return nil, errors.New("Fail buildStory :" + err.Error())
-		return nil, errors.New("Fail buildNLSLayers :" + err.Error())
-		return nil, errors.New("Fail buildLayerStyles :" + err.Error())
+		return nil, fmt.Errorf("failed to build scene: %w", err)
+		return nil, fmt.Errorf("failed to build story: %w", err)
+		return nil, fmt.Errorf("failed to build NLS layers: %w", err)
+		return nil, fmt.Errorf("failed to build layer styles: %w", err)

This change:

  1. Uses consistent capitalization
  2. Uses fmt.Errorf with %w for proper error wrapping
  3. Improves readability with proper spacing

Also applies to: 141-141, 149-149, 157-157

server/internal/usecase/interactor/asset.go (1)

108-127: Proper transactional update for asset project.

The Update method correctly applies a transaction when modifying an asset’s project association. Ensure that relevant validations (e.g., workspace ownership checks) remain up to date with potential future changes.

server/internal/adapter/gql/resolver_mutation_project.go (4)

183-199: Filter or sanitize parsed asset names before using them.

After retrieving asset URLs, the code extracts file names via path.Base(). Consider validating or sanitizing these names if derived from user-generated data. This helps avoid complications if the file name contains unexpected characters or malicious patterns.


247-269: Check for large memory usage when calling bytes.Replace repeatedly.

When iterating over multiple assets, repeated calls to bytes.Replace can become expensive in memory usage. If this operation scales significantly (e.g., thousands of assets), consider streaming or more incremental replacements.


346-354: Potential advanced validation for project data.

unmarshalProject simply performs JSON unmarshalling and returns a map. Depending on the complexity of your project data, you might consider more robust schema-based validation or typed structures for future maintainability.


356-370: Ensure concurrency-safety if unmarshal logic is reused widely.

unmarshalAssets is straightforward. If multiple goroutines eventually process the same data, confirm that no race conditions exist modifying the returned map.

server/e2e/gql_project_export_import_test.go (2)

21-63: Consider renaming the helper function for clarity.
The function call at line 29 references exporProject, which appears to be a misspelling of "exportProject."

- fileName := exporProject(t, e, pID.String())
+ fileName := exportProject(t, e, pID.String())

58-61: Improve error handling when removing temporary files.
Although the error from os.Remove is asserted via assert.Nil(t, err), consider using assert.NoError(t, err) or providing a more descriptive message to clarify any failures.

server/e2e/seeder.go (2)

125-167: Check file path safety and error handling.
While opening and uploading the file, ensure that potential directory traversal or permission issues are addressed. A sanitization or path validation step could prevent unexpected usage.


283-312: Consider adding more test coverage for new storytelling data.
The addStory function seeds a single test page. Confirm if additional scenarios (e.g., multiple pages, blocks) should also be tested to reflect real usage.

server/internal/usecase/interactor/scene.go (2)

711-715: Add error handling or fallback logic for malformed URLs.
When urlStr is parsed at line 711, errors are silently ignored (_ = url.Parse(urlStr)). Consider logging or returning an error if the URL cannot be parsed.


765-771: Confirm allowed file types for block source.
Similar to widget icons, ensure the data in field.Value() matches acceptable file types, rather than allowing arbitrary external references.

server/internal/adapter/gql/gqlmodel/models_gen.go (1)

581-589: Consider typed structures instead of any

Using any grants flexibility but loosens type safety. If possible, consider a more specific or typed approach to reduce potential runtime errors and improve clarity.

server/internal/infrastructure/mongo/mongodoc/asset.go (1)

17-17: LGTM! Consider adding a comment for the Project field.

The Project field is well-placed in the struct. Consider adding a comment similar to the Team field to document its purpose.

-	Project     *string
+	Project     *string // Project ID associated with the asset
server/internal/adapter/gql/resolver_mutation_asset.go (2)

19-26: LGTM! Consider consolidating error variable names.

The project ID handling is well-implemented. Consider using consistent error variable names across the file.

-	if err != nil {
+	if err2 := err; err2 != nil {

41-62: LGTM! Clean implementation of UpdateAsset mutation.

The UpdateAsset mutation is well-structured with proper error handling. Consider adding a comment describing the mutation's purpose.

+// UpdateAsset updates an asset's project association
 func (r *mutationResolver) UpdateAsset(ctx context.Context, input gqlmodel.UpdateAssetInput) (*gqlmodel.UpdateAssetPayload, error) {
server/internal/adapter/gql/gqlmodel/convert_project.go (1)

127-137: Consider adding error details in JSON conversion.

The function silently returns nil on JSON conversion errors, which might make debugging harder.

Consider returning an error or logging the conversion failures:

 func ToProjectExportFromJSON(data map[string]any) *ProjectExport {
 	var p ProjectExport
 	bytes, err := json.MarshalIndent(data, "", "  ")
 	if err != nil {
+		log.Printf("Failed to marshal project export data: %v", err)
 		return nil
 	}
 	if err := json.Unmarshal(bytes, &p); err != nil {
+		log.Printf("Failed to unmarshal project export data: %v", err)
 		return nil
 	}
 	return &p
 }
server/internal/infrastructure/mongo/asset.go (2)

72-74: Consider using a builder pattern for filter construction.

The current filter construction with direct assignments could be made more maintainable using a builder pattern or helper function, especially as more conditions are added.

Also applies to: 76-80


87-94: Improve error handling for bucket pattern.

The bucket pattern determination logic could benefit from better error handling and clearer conditions.

Consider extracting this logic into a separate function with proper error handling:

+func determineBucketPattern(ctx context.Context) string {
+	pattern := adapter.CurrentHost(ctx)
+	switch {
+	case pattern == "":
+		return "example.com" // e2e test
+	case strings.Contains(pattern, "localhost"):
+		return "localhost"
+	default:
+		return "visualizer"
+	}
+}
server/internal/app/app.go (1)

164-168: Consider adding documentation for the dummy auth middleware.

While the implementation is correct, adding documentation would help explain the purpose and use case of this middleware.

Add documentation above the function:

+// AuthMiddlewareDummy returns a middleware that passes requests through without authentication.
+// This is used when authentication service is disabled.
 func AuthMiddlewareDummy() (func(http.Handler) http.Handler, error) {
     return func(next http.Handler) http.Handler {
         return next
     }, nil
 }
server/e2e/common.go (4)

35-43: Consider using a more descriptive variable name than fr.

The global variable fr is not self-documenting. A more descriptive name like globalFileRef would better convey its purpose.

-var (
-  fr                 *gateway.File
-  disabledAuthConfig = &config.Config{
-    Origins: []string{"https://example.com"},
-    AuthSrv: config.AuthSrvConfig{
-      Disabled: true,
-    },
-  }
-)
+var (
+  globalFileRef      *gateway.File
+  disabledAuthConfig = &config.Config{
+    Origins: []string{"https://example.com"},
+    AuthSrv: config.AuthSrvConfig{
+      Disabled: true,
+    },
+  }
-)

51-71: Consider using a more idiomatic error handling pattern.

The function could benefit from a more idiomatic error handling pattern by returning the error first.

-func initRepos(t *testing.T, useMongo bool, seeder Seeder) (repos *repo.Container, file gateway.File) {
+func initRepos(t *testing.T, useMongo bool, seeder Seeder) (*repo.Container, gateway.File, error) {
   ctx := context.Background()

   if useMongo {
     db := mongotest.Connect(t)(t)
     accountRepos := lo.Must(accountmongo.New(ctx, db.Client(), db.Name(), false, false, nil))
-    repos = lo.Must(mongo.New(ctx, db, accountRepos, false))
+    repos, err := mongo.New(ctx, db, accountRepos, false)
+    if err != nil {
+      return nil, nil, fmt.Errorf("failed to create mongo repo: %w", err)
+    }
   } else {
     repos = memory.New()
   }

-  file = lo.Must(fs.NewFile(afero.NewMemMapFs(), "https://example.com/"))
-  fr = &file
+  file, err := fs.NewFile(afero.NewMemMapFs(), "https://example.com/")
+  if err != nil {
+    return nil, nil, fmt.Errorf("failed to create file: %w", err)
+  }
+  globalFileRef = &file

   if seeder != nil {
     if err := seeder(ctx, repos, file); err != nil {
-      t.Fatalf("failed to seed the db: %s", err)
+      return nil, nil, fmt.Errorf("failed to seed the db: %w", err)
     }
   }

-  return repos, file
+  return repos, file, nil
}

73-82: Consider initializing file in a separate function.

The file initialization logic could be extracted into a separate function for better reusability and testability.

+func initFile() (gateway.File, error) {
+  return fs.NewFile(afero.NewMemMapFs(), "https://example.com/")
+}

 func initGateway() *gateway.Container {
   if fr == nil {
+    file, err := initFile()
+    if err != nil {
+      return &gateway.Container{}
+    }
     return &gateway.Container{
-      File: lo.Must(fs.NewFile(afero.NewMemMapFs(), "https://example.com/")),
+      File: file,
     }
   }
   return &gateway.Container{
     File: *fr,
   }
 }

627-649: Consider adding error documentation and handling external URLs.

The AddZipAsset function could benefit from better documentation and explicit handling of external URLs.

+// AddZipAsset adds an asset to a zip file. If the asset path is an external URL,
+// the function silently skips it and returns nil.
+// Returns an error if reading the asset or writing to the zip file fails.
 func AddZipAsset(ctx context.Context, file gateway.File, zipWriter *zip.Writer, path string) error {
   fileName := strings.TrimPrefix(path, "/assets/")
+  
+  // Skip if the path is an external URL
+  if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
+    return nil
+  }
+  
   stream, err := file.ReadAsset(ctx, fileName)
   if err != nil {
     return nil // skip if external URL
   }
   defer func() {
     if cerr := stream.Close(); cerr != nil {
-      fmt.Printf("Error closing file: %v\n", cerr)
+      // Log the error but don't fail the operation
+      fmt.Printf("Warning: error closing file %s: %v\n", fileName, cerr)
     }
   }()
   zipEntryPath := fmt.Sprintf("assets/%s", fileName)
   zipEntry, err := zipWriter.Create(zipEntryPath)
   if err != nil {
-    return err
+    return fmt.Errorf("failed to create zip entry for %s: %w", fileName, err)
   }
   _, err = io.Copy(zipEntry, stream)
   if err != nil {
     _ = stream.Close()
-    return err
+    return fmt.Errorf("failed to copy asset %s to zip: %w", fileName, err)
   }
   return nil
 }
server/internal/usecase/interactor/project.go (1)

513-533: Consider adding context timeout for export operations.

Long-running export operations should have a timeout to prevent resource exhaustion.

 func (i *Project) ExportProjectData(ctx context.Context, projectID id.ProjectID, zipWriter *zip.Writer, operator *usecase.Operator) (*project.Project, error) {
+  // Add timeout for export operation
+  ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
+  defer cancel()
 
   prj, err := i.projectRepo.FindByID(ctx, projectID)
   if err != nil {
     return nil, errors.New("project " + err.Error())
   }
   if prj.IsDeleted() {
-    fmt.Printf("Error Deleted project: %v\n", prj.ID())
+    return nil, fmt.Errorf("project %s is deleted", prj.ID())
   }
server/e2e/gql_custom_property_test.go (1)

347-762: Consider extracting GraphQL fragments into a separate file.

The GraphQL query has grown significantly with multiple fragments. Consider moving these fragments to a separate file to improve maintainability and reusability.

Example structure:

server/
  e2e/
    fragments/
      property.graphql
      widget.graphql
      story.graphql
    queries/
      scene.graphql

This would make the test file more focused and the fragments reusable across different tests.

server/Makefile (1)

85-90: Consider a more descriptive target name and add safety checks.

The run-reset target performs destructive operations. Consider:

  1. Renaming to reset-mongo-data for clarity
  2. Adding safety checks before data removal

Apply this diff to improve the target:

-run-reset:
+reset-mongo-data:
+	@echo "WARNING: This will delete all MongoDB data. Are you sure? (y/n)" && read ans && [ $$ans = y ]
 	docker stop reearth-visualizer-reearth-mongo-1
 	rm -rf ../mongo
 	rm -rf data
 	make run-db
 	make mockuser
server/e2e/gql_storytelling_test.go (1)

231-413: Publication workflow validation
This test rigorously checks publishing a story, including reading back the published JSON data via file storage and HTTP endpoint. The scenario is comprehensive and confirms the final structure of the published story, including default GA settings. Consider adding a variation for GA enabled to ensure coverage of all states.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ad2fc9 and 7c10081.

⛔ Files ignored due to path filters (4)
  • server/e2e/test.zip is excluded by !**/*.zip
  • web/src/services/gql/__gen__/fragmentMatcher.json is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/gql.ts is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/graphql.ts is excluded by !**/__gen__/**
📒 Files selected for processing (107)
  • server/Makefile (3 hunks)
  • server/e2e/common.go (7 hunks)
  • server/e2e/dataset_export_test.go (0 hunks)
  • server/e2e/gql_asset_test.go (6 hunks)
  • server/e2e/gql_custom_property_test.go (1 hunks)
  • server/e2e/gql_layer_test.go (0 hunks)
  • server/e2e/gql_me_test.go (1 hunks)
  • server/e2e/gql_nlslayer_test.go (0 hunks)
  • server/e2e/gql_project_export_import_test.go (3 hunks)
  • server/e2e/gql_project_export_test.go (0 hunks)
  • server/e2e/gql_project_test.go (6 hunks)
  • server/e2e/gql_scene_test.go (4 hunks)
  • server/e2e/gql_storytelling_test.go (6 hunks)
  • server/e2e/gql_user_test.go (2 hunks)
  • server/e2e/gql_validate_geojson_test.go (1 hunks)
  • server/e2e/mock_test.go (1 hunks)
  • server/e2e/ping_test.go (1 hunks)
  • server/e2e/seeder.go (5 hunks)
  • server/go.mod (1 hunks)
  • server/gql/asset.graphql (5 hunks)
  • server/gql/cluster.graphql (0 hunks)
  • server/gql/dataset.graphql (0 hunks)
  • server/gql/layer.graphql (0 hunks)
  • server/gql/plugin.graphql (2 hunks)
  • server/gql/property.graphql (0 hunks)
  • server/gql/scene.graphql (1 hunks)
  • server/gql/storytelling.graphql (3 hunks)
  • server/gql/tag.graphql (0 hunks)
  • server/gql/workspace.graphql (2 hunks)
  • server/gqlgen.yml (0 hunks)
  • server/internal/adapter/gql/context.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/dataloader.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_layer.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go (5 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project.go (2 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_scene.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_tag.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (9 hunks)
  • server/internal/adapter/gql/loader.go (0 hunks)
  • server/internal/adapter/gql/loader_asset.go (2 hunks)
  • server/internal/adapter/gql/loader_dataset.go (0 hunks)
  • server/internal/adapter/gql/loader_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset_schema.go (0 hunks)
  • server/internal/adapter/gql/resolver_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_asset.go (3 hunks)
  • server/internal/adapter/gql/resolver_mutation_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (6 hunks)
  • server/internal/adapter/gql/resolver_mutation_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_storytelling.go (1 hunks)
  • server/internal/adapter/gql/resolver_mutation_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_property.go (0 hunks)
  • server/internal/adapter/gql/resolver_property_test.go (0 hunks)
  • server/internal/adapter/gql/resolver_query.go (1 hunks)
  • server/internal/adapter/gql/resolver_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_team.go (1 hunks)
  • server/internal/app/app.go (2 hunks)
  • server/internal/app/auth_client.go (1 hunks)
  • server/internal/infrastructure/fs/file.go (2 hunks)
  • server/internal/infrastructure/fs/file_test.go (3 hunks)
  • server/internal/infrastructure/memory/asset.go (1 hunks)
  • server/internal/infrastructure/mongo/asset.go (2 hunks)
  • server/internal/infrastructure/mongo/mongodoc/asset.go (3 hunks)
  • server/internal/infrastructure/mongo/mongodoc/scene.go (0 hunks)
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go (4 hunks)
  • server/internal/usecase/interactor/asset.go (6 hunks)
  • server/internal/usecase/interactor/layer.go (0 hunks)
  • server/internal/usecase/interactor/nlslayer.go (3 hunks)
  • server/internal/usecase/interactor/project.go (5 hunks)
  • server/internal/usecase/interactor/project_test.go (0 hunks)
  • server/internal/usecase/interactor/scene.go (5 hunks)
  • server/internal/usecase/interactor/scene_test.go (0 hunks)
  • server/internal/usecase/interactor/storytelling.go (1 hunks)
  • server/internal/usecase/interfaces/asset.go (2 hunks)
  • server/internal/usecase/interfaces/project.go (2 hunks)
  • server/internal/usecase/interfaces/story.go (1 hunks)
  • server/internal/usecase/repo/asset.go (1 hunks)
  • server/pkg/asset/asset.go (2 hunks)
  • server/pkg/asset/builder.go (1 hunks)
  • server/pkg/asset/id.go (1 hunks)
  • server/pkg/layer/encoding/exporter.go (1 hunks)
  • server/pkg/layer/layerops/processor_test.go (0 hunks)
  • server/pkg/nlslayer/feature.go (0 hunks)
  • server/pkg/nlslayer/feature_test.go (1 hunks)
  • server/pkg/scene/builder/builder.go (1 hunks)
  • server/pkg/scene/builder/builder_test.go (0 hunks)
  • server/pkg/scene/builder/encoder.go (1 hunks)
  • server/pkg/scene/builder/encoder_test.go (0 hunks)
  • server/pkg/scene/builder/scene.go (1 hunks)
  • server/pkg/scene/builder/story.go (1 hunks)
  • server/pkg/scene/builder_test.go (0 hunks)
  • server/pkg/storytelling/story.go (4 hunks)
  • server/pkg/storytelling/story_bulider.go (1 hunks)
  • web/src/services/api/propertyApi/utils.ts (1 hunks)
⛔ Files not processed due to max files limit (6)
  • web/src/services/api/sceneApi.ts
  • web/src/services/gql/fragments/dataset.ts
  • web/src/services/gql/fragments/index.ts
  • web/src/services/gql/fragments/layer.ts
  • web/src/services/gql/fragments/property.ts
  • web/src/services/gql/queries/scene.ts
💤 Files with no reviewable changes (47)
  • server/internal/adapter/gql/gqlmodel/convert.go
  • server/pkg/scene/builder_test.go
  • server/pkg/layer/layerops/processor_test.go
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go
  • server/internal/adapter/gql/gqldataloader/dataloader.go
  • server/e2e/gql_nlslayer_test.go
  • server/internal/adapter/gql/context.go
  • server/internal/usecase/interactor/project_test.go
  • server/pkg/nlslayer/feature.go
  • server/pkg/scene/builder/encoder_test.go
  • server/pkg/scene/builder/builder_test.go
  • server/internal/adapter/gql/gqlmodel/convert_scene.go
  • server/internal/adapter/gql/resolver_property_test.go
  • server/e2e/dataset_export_test.go
  • server/internal/usecase/interactor/scene_test.go
  • server/internal/adapter/gql/gqlmodel/models.go
  • server/gql/property.graphql
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go
  • server/e2e/gql_project_export_test.go
  • server/internal/adapter/gql/resolver_mutation_scene.go
  • server/gql/cluster.graphql
  • server/internal/infrastructure/mongo/mongodoc/scene.go
  • server/internal/adapter/gql/resolver_layer.go
  • server/gqlgen.yml
  • server/gql/dataset.graphql
  • server/internal/usecase/interactor/layer.go
  • server/internal/adapter/gql/resolver_property.go
  • server/internal/adapter/gql/resolver_scene.go
  • server/e2e/gql_layer_test.go
  • server/internal/adapter/gql/loader.go
  • server/internal/adapter/gql/gqlmodel/convert_tag.go
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go
  • server/internal/adapter/gql/gqlmodel/convert_layer.go
  • server/internal/adapter/gql/resolver_mutation_dataset.go
  • server/gql/layer.graphql
  • server/internal/adapter/gql/resolver_dataset.go
  • server/gql/tag.graphql
  • server/internal/adapter/gql/resolver_mutation_tag.go
  • server/internal/adapter/gql/resolver_dataset_schema.go
  • server/internal/adapter/gql/resolver_tag.go
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go
  • server/internal/adapter/gql/resolver_mutation_layer.go
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go
  • server/internal/adapter/gql/loader_dataset.go
  • server/internal/adapter/gql/loader_tag.go
✅ Files skipped from review due to trivial changes (1)
  • server/pkg/scene/builder/encoder.go
🧰 Additional context used
🧠 Learnings (2)
server/internal/adapter/gql/resolver_mutation_project.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:215-217
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project import functionality, the file names are unique and can be safely replaced uniformly using `bytes.Replace`.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:0-0
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project export functionality, the ZIP file is deleted after saving to storage.
server/e2e/gql_project_export_import_test.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:48-80
Timestamp: 2024-11-12T15:21:04.151Z
Learning: In `server/e2e/gql_import_export_test.go`, test functions like `TestCallImportProject` may have disabled assertions due to problems with GraphQL.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:12-44
Timestamp: 2024-11-12T15:21:04.150Z
Learning: The response body assertions in `TestCallExportProject` are disabled due to existing problems with GraphQL.
🔇 Additional comments (96)
server/internal/adapter/gql/resolver_team.go (1)

27-34: LGTM! Method signature and implementation updated correctly for project-based asset filtering.

The changes properly integrate project-based asset filtering by:

  1. Adding the optional projectID parameter to the method signature
  2. Correctly passing it to the asset loader's FindByWorkspace method

However, I notice a discrepancy between these changes and the PR objectives. The PR description mentions adding Google Analytics support to the Story component, but these changes are about project-based asset operations.

Likely an incorrect or invalid review comment.

server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go (1)

11-13: LGTM! Improved API design with explicit ID generation.

The consistent replacement of NewFeatureWithNewId with explicit ID generation using NewFeature(nlslayer.NewFeatureID(), ...) improves code clarity and provides better control over feature creation. This change aligns well with the principle of explicit over implicit behavior.

Also applies to: 21-23, 31-33, 41-43, 51-53

web/src/services/api/propertyApi/utils.ts (1)

247-248: Verify if these changes belong to this PR.

These changes appear unrelated to the PR's stated objective of adding Google Analytics support to the Story component. Please verify if these changes should be part of this PR or moved to a separate one.

server/gql/scene.graphql (1)

50-50: LGTM!

The formatting change improves readability by adding a space after the Query type extension declaration.

server/pkg/scene/builder/scene.go (1)

27-40: LGTM!

The function correctly accepts and populates the Google Analytics fields.

server/internal/adapter/gql/gqlmodel/convert_asset.go (3)

12-16: LGTM! Well-implemented project ID handling.

The code safely handles the optional project ID using proper nil checks and pointer semantics.


22-22: LGTM! Correct ProjectID field update.

The ProjectID field is properly updated to use the new pid variable, maintaining consistency with the rest of the struct initialization.


1-63:

❓ Verification inconclusive

Verify PR objectives alignment.

The changes in this file implement project ID support for assets, which appears to be unrelated to the stated PR objective of adding Google Analytics support to the Story component. Please clarify if this is an unintended inclusion or if the PR description needs to be updated.

Let's verify if there are any Story-related or Google Analytics changes that should be associated with this PR:


🏁 Script executed:

#!/bin/bash
# Search for Story or Google Analytics related changes
echo "Searching for Story-related changes..."
rg -l "Story|GoogleAnalytics|enableGa|trackingId"

echo "Checking GraphQL schema changes..."
fd -g "*.graphql" --exec cat {}

Length of output: 45070


Clarify PR Objectives: Verify Inclusion of Asset Project ID Changes
The conversion functions in server/internal/adapter/gql/gqlmodel/convert_asset.go now include support for project IDs on assets. However, the PR description indicates that the changes target adding Google Analytics support for the Story component. Please clarify whether the asset project ID changes are intentionally part of this PR or if the PR description should be updated to reflect multiple objectives.

server/pkg/scene/builder/builder.go (2)

91-91: LGTM: Google Analytics parameters properly integrated.

The addition of enableGa and trackingId parameters to the builder methods is well-structured and consistently propagated through the call chain.

Also applies to: 128-128, 165-165


176-179:

❓ Verification inconclusive

Verify layer export functionality after encoder removal.

The removal of the encoder field and layers parameter suggests a significant architectural change. While the layer export functionality is maintained through exporter.ExportLayerByID, we should verify that all layer data is still properly processed.

Also applies to: 181-181


🏁 Script executed:

#!/bin/bash
# Search for any remaining references to the encoder or layer processing
rg -A 5 'encoder|layers' server/pkg/scene/

Length of output: 4771


Verify layer export functionality remains intact post-refactor.

The removal of the encoder field from the Builder struct and the associated layers parameter in the sceneJSON call has been confirmed. In server/pkg/scene/builder/builder.go (lines 176–179), the export of layers now directly calls b.exporter.ExportLayerByID(ctx, b.scene.RootLayer()). Additionally, our code search shows that layer processing is still referenced in other modules (e.g., nlsLayer.go, dataset_migrator.go, and plugin_migrator.go).

Please ensure that your existing tests or additional manual validations confirm that all layer data is exported correctly despite these architectural changes.

server/internal/usecase/interactor/asset.go (3)

39-44: Ensure consistent query for project-level asset retrieval.

The new FindByWorkspaceProject method now filters assets by both workspace and project. Confirm that any higher-level logic invoking this method is aligned with these parameters to prevent unintended results.


91-91: Good addition of project association during asset creation.

Adding .Project(inp.ProjectID) during asset building allows for clear alignment of new assets to a specific project. No further issues spotted.


154-182: Validate file import logic for large or unusual file names.

In ImportAssetFiles, setting the file path to realName and then deriving a new name via path.Base(realName) is logically sound. However, certain edge cases (extremely long file names or path injection attempts) might require additional sanitization or validation checks.

server/e2e/gql_asset_test.go (3)

15-119: Inconsistent test coverage for coreSupport flags.

Within TestCoreSupportGetAssets, multiple assets are created (some with coreSupport=true, some with coreSupport=false), but the final check at lines 115–118 forces coreSupport to be true for all returned assets. This seems inconsistent. Confirm whether the test environment or the logic is intended to return only assets with coreSupport=true, or whether additional test validations for coreSupport=false are needed.


122-179: Good approach for testing project-based asset association.

TestAssociateProjectGetAssets thoroughly covers the workflow of creating assets under different projects, reassigning them, and verifying retrieval. It effectively demonstrates correct usage of projectId. Ensure edge cases (like changing projectId from one project to another multiple times) are also considered if relevant for your application.


181-350: GraphQL mutation structures and helper functions look consistent.

The additions to the GraphQL schema and supporting test functions (updateAsset, removeAsset, getAssets) properly handle projectId and confirm the new workflow for associating assets with projects. No immediate issues found; the code is cohesive and logically correct.

server/internal/adapter/gql/resolver_mutation_project.go (4)

130-155: Perform additional cleanup and checks in export process.

Here, you create a ZIP file named by the lowercased project ID, then remove the file locally after uploading. Verify that error handling logic covers partial uploads or I/O errors that occur mid-transfer.


225-233: Transaction handling is correct, but carefully finalize error propagation.

By deferring tx.End(ctx), you appropriately finalize or roll back changes. Validate that any mid-operation error sets the correct state, ensuring no partial writes remain.


335-344: Well-structured scene ID replacement.

replaceOldSceneID cleanly identifies and replaces the scene ID in the exported JSON data. This approach is concise and meets typical needs.


372-382: Adequate breakdown of plugins and scene data.

unmarshalPluginsScene nicely separates plugins, scenes, and schemas. Keep an eye on error handling and typed conversions to avoid unexpected panics in case of invalid input.

server/e2e/gql_project_export_import_test.go (1)

71-101: Ensure robust handling of unexpected shape mismatches.
The compareValue function logs a failure if lines differ but continues matching other lines. Double-check whether the test should abort early or continue checking all lines for better debugging.

server/e2e/seeder.go (3)

58-71: Validate presence of workspace ID before base setup.
Currently, baseSeeder calls baseSetup assuming the workspace from the user has been properly established. If a new workspace were required, ensure a prior check or creation logic is in place.


395-448: Validate schema version when creating layers.
In addLayerSimple, a JSON schema is unmarshaled. Confirm that this schema is compatible with current and future versions if there are multiple schema definitions.


512-527: Double-check UUID generation for compliance.
The generateUUID method uses a standard approach. Ensure no collisions occur in concurrent seeds or if any external constraints exist on allowable ID formats.

server/internal/usecase/interactor/scene.go (3)

22-22: GA integration aligns with PR objectives.
The import of the storytelling package and usage of EmitGA references (prj.EnableGA(), prj.TrackingID()) conform to the stated PR objective of enabling Google Analytics within stories.


680-683: Ensure correct selection of multiple stories.
Here, only the first story is selected if multiple exist. If multi-story usage is expected, consider handling all stories or clarifying that only the first is relevant.


740-745: Validate button icon URLs beyond presence.
A valid URL is assumed. If the URL is invalid or points to an inaccessible path, AddZipAsset might fail. Add robust checks or logs for debugging.

server/internal/adapter/gql/gqlmodel/models_gen.go (7)

138-138: Verify usage of ProjectID

The new optional ProjectID field on the Asset type looks good, but please ensure that wherever it's set, the user has the necessary permissions, and that it's validated to avoid potential orphan references.


905-916: Reuse previous feedback about any usage

Same concern applies here as in the MergedPropertyField. If feasible, reduce usage of any to make the schema more robust and discoverable.


919-922: Looks good: clarified dataset linkage

These optional fields enhance clarity around linking property fields to dataset schemas. No further issues found.


1135-1149: Double-check references to newly added fields in Scene

The new arrays for Stories, Styles, and related references look consistent. Verify that these additions are properly tested for scenarios such as empty arrays or permission checks.


1221-1222: GA fields aligned with PR objectives

Adding EnableGa and TrackingID directly to the Story type as mandatory fields matches the PR requirements for GA support.


1332-1340: Validate new projectId references in asset updates

Ensure that the resolvers or services handling UpdateAssetInput and its payload account for the newly introduced projectId field and that it’s guarded by applicable access checks.


1459-1460: Optional GA parameters well-defined

The optional enableGa and trackingId fields in UpdateStoryInput follow best practices for partial updates and look consistent with the rest of the schema.

server/e2e/gql_me_test.go (1)

8-8: Confirm continuity of server configuration

Switching from a custom-configured server to Server(t, baseSeeder) may alter environment setups (e.g., allowed origins). Please confirm that test security or cross-origin constraints remain correct under this new initialization method.

server/e2e/ping_test.go (1)

9-9: Ping test initialization streamlined

Using ServerPingTest(t) simplifies setup without apparent downsides. The new approach looks fine, and the ping routes remain thoroughly tested.

server/pkg/layer/encoding/exporter.go (1)

10-13:

❓ Verification inconclusive

Verify the impact of removing the Encoder field.

The removal of the Encoder field from the Exporter struct could be a breaking change that affects layer encoding functionality.


🏁 Script executed:

#!/bin/bash
# Search for any references to the Encoder field
rg -A 5 "Encoder\s+Encoder" 

# Search for any tests that might need updating
rg -A 5 "TestExporter.*Encode"

Length of output: 62


Verify the impact of removing the Encoder field

  • The Encoder field has been removed from the Exporter struct and the Encode method now returns nil, which suggests that the encoding functionality was effectively disabled.
  • Automated searches for references to the removed Encoder field and for tests related to TestExporter.*Encode produced no output, so we cannot conclusively determine the impact from these searches alone.
  • Action: Please perform a manual verification to ensure that no other parts of the codebase depend on the previous encoding functionality and confirm that tests have been adequately updated to cover these changes.
server/pkg/asset/id.go (1)

10-26: LGTM! Well-structured additions for project ID support.

The new type alias and variables for ProjectID follow the existing patterns and maintain consistency with other ID types in the package.

server/pkg/nlslayer/feature_test.go (2)

9-24: LGTM! Test updated correctly for API changes.

The test has been properly updated to use NewFeature with an explicit ID, maintaining good coverage of the feature creation functionality.


1-1: Inconsistency between PR objectives and actual changes.

The PR description states that it adds Google Analytics parameters to the Story component, but the actual changes focus on project-based asset management and various refactoring. Please clarify this discrepancy.

Likely an incorrect or invalid review comment.

server/e2e/mock_test.go (1)

11-11: LGTM, but verify PR objectives.

The server initialization has been simplified using ServerMockTest, which is a good improvement. However, this change appears unrelated to the PR's stated objective of adding Google Analytics support to the Story component.

Likely an incorrect or invalid review comment.

server/pkg/asset/asset.go (2)

18-18: LGTM: Project field addition.

The new project field is correctly defined as a pointer to ProjectID, allowing for optional project association.

Likely an incorrect or invalid review comment.


34-40: LGTM: Project getter and setter methods.

The getter and setter methods are well-implemented, following proper encapsulation practices.

server/internal/usecase/interfaces/asset.go (2)

28-28: LGTM: Project ID parameter addition.

The ProjectID field is correctly added to CreateAssetParam, maintaining consistency with the asset struct changes.

Likely an incorrect or invalid review comment.


39-43: LGTM: Method signature updates.

The method signatures have been correctly updated to include project context:

  • FindByWorkspaceProject now includes project filtering
  • Update method added for project association
  • ImportAssetFiles replaces UploadAssetFile with project support
server/pkg/asset/builder.go (1)

57-60: LGTM: Project builder method.

The Project method is well-implemented, following the builder pattern and maintaining the fluent interface style.

Likely an incorrect or invalid review comment.

server/internal/infrastructure/mongo/mongodoc/asset.go (2)

35-40: LGTM! Clean implementation of project ID handling.

The project ID handling is well-implemented with proper nil checks and string conversion.


65-72: LGTM! Proper error handling for project ID conversion.

The error handling for project ID conversion is thorough and follows best practices.

server/pkg/storytelling/story_bulider.go (2)

124-127: LGTM! Consider adding validation in Build method.

The EnableGa method is well-implemented. Consider adding validation in the Build method to ensure trackingID is set when enableGa is true.


129-132:

✅ Verification successful

LGTM! Consider adding validation for tracking ID format.

The TrackingID method is well-implemented. Consider adding validation for the Google Analytics tracking ID format (UA-XXXXX-Y or G-XXXXXX).


🌐 Web query:

What is the format of a Google Analytics tracking ID?

💡 Result:

Google Analytics tracking IDs come in two primary formats depending on the version of Analytics used:


1. Universal Analytics (Legacy)

Format: UA-XXXXXXXXX-Y

  • Starts with "UA-", followed by 7–14 digits (XXXXXXXXX), a hyphen, and a 1–3 digit suffix (Y)
  • Example: UA-123456789-1
    This ID was used for older Universal Analytics properties and is being phased out[4][5][6].

2. Google Analytics 4 (GA4)

Format: G-XXXXXXXXXX

  • Starts with "G-", followed by 8–12 alphanumeric characters
  • Example: G-A1B2CD34EF
    This newer Measurement ID replaced tracking IDs in GA4 for connecting websites/apps to analytics properties[1][2][4][8].

Key Differences

Feature Universal Analytics (UA-) GA4 (G-)
Purpose Legacy tracking system Modern cross-platform
Structure Numeric-only suffix Alphanumeric code
Installation Via tracking code Via Google tag
Data Collection Session-based Event-based

For GA4 implementations using Google Ads or other services, you might also encounter IDs starting with "AW-" (e.g., AW-123456789)[3][9]. Always verify your ID through Admin > Data Streams > Web Stream Details in GA4[1][4][9].

Citations:


LGTM! The TrackingID method is well-implemented. However, to improve the robustness of the code, please consider adding validation logic that verifies the tracking ID conforms to accepted Google Analytics formats. For example:

  • Universal Analytics (Legacy): Ensure it matches the pattern UA-XXXXXXXXX-Y, where the numeric part contains 7–14 digits and the suffix consists of 1–3 digits (e.g., UA-123456789-1).
  • Google Analytics 4 (GA4): Ensure it matches the pattern G-XXXXXXXXXX, starting with "G-" followed by 8–12 alphanumeric characters (e.g., G-A1B2CD34EF).

Adding such validation would help catch any improperly formatted IDs early in the process.

File: server/pkg/storytelling/story_bulider.go
Lines: 129-132

server/internal/usecase/interfaces/project.go (1)

74-75: LGTM! Clear and descriptive method renaming.

The method renaming from ExportProject/ImportProject to ExportProjectData/ImportProjectData better reflects their purpose. The signature changes improve type safety with idx.ID.

server/internal/adapter/gql/loader_asset.go (2)

39-54: LGTM! Project ID handling is well-implemented.

The addition of project ID filtering is implemented correctly with proper error handling and null checks.


60-66: Performance optimization in edges initialization.

Good optimization by pre-allocating the edges slice with the known size and using direct index assignment.

server/internal/infrastructure/memory/asset.go (1)

49-59: LGTM! Project filtering logic is well-implemented.

The implementation correctly handles both project-specific and workspace-only filtering scenarios.

server/e2e/gql_scene_test.go (2)

16-16: LGTM! Response object access pattern is consistent.

The updates to use r.Object().Value(...) maintain consistency across all response handling.

Also applies to: 42-42, 71-73


77-100: Verify the external image URL in tests.

The function uses a hardcoded test URL which might not be available during test execution.

Consider using a more reliable test URL or mocking the external image endpoint.

server/internal/adapter/gql/gqlmodel/convert_project.go (1)

108-113: LGTM! ProjectExport type is well-structured.

The type definition includes essential fields with proper JSON tags and types.

server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1)

35-36: LGTM! GA fields added correctly.

The Google Analytics fields EnableGa and TrackingID are properly integrated into the Story struct, following the existing pattern and maintaining consistency with other fields.

server/e2e/gql_validate_geojson_test.go (1)

186-186: LGTM! Function call updated correctly.

The createAssetFromFileData call has been properly updated to include the new projectId parameter as nil, maintaining backward compatibility while supporting the new project-specific asset handling feature.

server/internal/usecase/interfaces/story.go (1)

38-39: LGTM! GA fields added correctly to UpdateStoryInput.

The optional GA fields EnableGa and TrackingID are properly added as pointer types, consistent with other optional fields in the struct.

server/internal/app/auth_client.go (1)

105-107: LGTM! Good improvement to logging behavior.

The conditional logging for e2e users helps reduce noise in logs during testing.

Also applies to: 115-117

server/pkg/storytelling/story.go (2)

41-42: LGTM! Well-structured Google Analytics integration.

The implementation properly encapsulates GA-related fields with appropriate getters and setters, aligning with the PR objectives.

Also applies to: 134-140, 190-196


57-62: LGTM! Good defensive programming.

The addition of the nil check in the Pages() method prevents potential nil pointer dereferences.

server/internal/adapter/gql/resolver_query.go (1)

15-17: LGTM! Clean implementation of project-specific asset filtering.

The addition of the optional projectId parameter is well-implemented and follows GraphQL best practices.

server/internal/infrastructure/mongo/mongodoc/storytelling.go (3)

34-35: LGTM! New fields added for Google Analytics support.

The addition of EnableGa and TrackingID fields to the StorytellingDocument struct aligns with the PR objectives.


87-88: LGTM! Proper initialization of GA fields.

The GA fields are correctly initialized from the Story instance.


197-198: LGTM! GA fields properly included in model conversion.

The GA fields are correctly included when building the Story model.

server/internal/app/app.go (1)

72-78: Improved auth configuration handling with disabled auth support.

The changes properly handle the case when authentication service is disabled by using a dummy middleware.

server/internal/infrastructure/fs/file.go (2)

113-113: Improved path validation for asset removal.

The path validation now correctly checks against the full asset directory path.


286-286: LGTM! Proper URL path construction for assets.

The URL path is correctly constructed by joining the base path with the asset directory and filename.

server/internal/infrastructure/fs/file_test.go (3)

116-117: LGTM! Updated test base URL to match new URL structure.

The test correctly uses the root path as the base URL, aligning with the changes in the implementation.


165-165: LGTM! Updated test base URL for asset removal.

The test correctly uses the root URL for asset removal validation.


316-316: LGTM! Updated test base URL for URL generation.

The test correctly uses the root URL for asset URL generation.

server/e2e/common.go (1)

45-45: LGTM! Clear type definition.

The Seeder type signature is well-defined and includes the necessary parameters.

server/internal/adapter/gql/resolver_mutation_storytelling.go (1)

58-59: LGTM! Google Analytics fields added correctly.

The EnableGa and TrackingID fields are properly added to the UpdateStoryInput structure, aligning with the PR objectives.

server/e2e/gql_project_test.go (1)

42-42:

❓ Verification inconclusive

Verify the updated test expectations.

The test assertions have been updated with new expected counts. Please ensure these changes align with the actual business logic changes:

  • Are there new default projects being created?
  • Has the pagination logic been modified?

Run the following script to verify the project counts:

Also applies to: 144-144, 585-585, 617-617, 620-620


🏁 Script executed:

#!/bin/bash
# Description: Search for project creation logic to understand the count changes.

# Test: Search for project creation in test setup
rg -A 5 "createProject|projects\(" server/e2e/

Length of output: 17956


Verify the updated test expectations for project counts in GraphQL tests

The updated assertions (e.g., at line 42 where edges.Length().IsEqual(2) is used) now expect revised project counts. The grep results confirm that project creation is handled consistently across various tests. Please double-check that these new default numbers and pagination behaviors (also affecting lines 144, 585, 617, and 620) are intentional and correctly aligned with the updated business logic. Specifically:

  • Confirm that the additional projects created in setup are expected in the query results.
  • Verify the pagination/filtering logic still returns the intended project subsets.
server/internal/usecase/interactor/storytelling.go (1)

188-194: LGTM! Google Analytics support added.

The implementation correctly handles the new Google Analytics parameters:

  • enableGa for enabling/disabling Google Analytics
  • trackingID for setting the tracking ID
server/internal/usecase/interactor/nlslayer.go (2)

859-861: LGTM! Improved feature ID generation.

The code now explicitly generates feature IDs using nlslayer.NewFeatureID(), making the ID generation more transparent and maintainable.


1109-1134: LGTM! Enhanced feature collection handling.

The code improvements include:

  • Better variable naming (features instead of i)
  • More explicit feature creation and property updates
  • Clearer structure for handling feature collections
server/gql/asset.graphql (3)

5-5: LGTM! Project association support added to Asset type.

The schema correctly adds optional projectId fields to both the Asset type and CreateAssetInput, allowing assets to be associated with specific projects.

Also applies to: 24-24


29-32: LGTM! Asset update functionality added.

The schema adds support for updating asset-project associations through:

  • New UpdateAssetInput type
  • New UpdateAssetPayload type
  • New updateAsset mutation

Also applies to: 49-52, 84-84


75-75: LGTM! Project-specific asset filtering added.

The assets query now supports filtering by projectId, enabling project-specific asset management.

server/gql/plugin.graphql (2)

26-28: Missing GA parameters in Story types.

The PR objectives mention adding Google Analytics parameters (enableGa and trackingId) to the Story component, but these fields are not present in the Story-related extension types.

Likely an incorrect or invalid review comment.


20-30:

❓ Verification inconclusive

Verify the impact of removing the Cluster extension type.

Please ensure that there are no existing plugins or extensions using the Cluster type before removing it.

Run this script to check for any usage:


🏁 Script executed:

#!/bin/bash
# Description: Search for any references to Cluster extension type
# Test: Look for any code using Cluster extension type. Expect: No results.

rg -i "type:\s*Cluster" 
rg -i "extensionType:\s*Cluster"

Length of output: 110


Action Required: Recheck the "Cluster" Reference Impact on Plugin Extensions

  • A grep search detected a reference to cluster in server/pkg/builtin/manifest.yml (e.g., type: cluster). This may indicate an existing usage that could be affected by removing the Cluster extension type.
  • The GraphQL schema in server/gql/plugin.graphql no longer defines Cluster, conflicting with the reference in the manifest.
  • Please manually verify whether this manifest reference is intended for a different purpose or if it should be updated or removed to align with the removal of the Cluster extension type.
server/gql/workspace.graphql (2)

8-14: LGTM! Good addition of project filtering.

The addition of the optional projectId parameter to the assets query allows for better filtering and organization of assets within teams.


15-21: Skipping formatting changes.

These changes are purely formatting-related and don't affect functionality.

Also applies to: 121-123

server/go.mod (1)

1-1: PR changes don't match objectives.

The PR objectives mention adding Google Analytics parameters (enableGa and trackingId) to the Story component, but the actual changes focus on:

  1. Removing Cluster from PluginExtensionType
  2. Adding projectId to Team.assets query
  3. Adding run-reset target to Makefile
  4. Updating go.mod dependencies

Please either:

  1. Update the PR description to match the actual changes, or
  2. Add the missing GA-related changes to the Story component

Likely an incorrect or invalid review comment.

server/e2e/gql_storytelling_test.go (6)

15-55: Thorough end-to-end coverage for Story creation and updates
This test effectively covers creation, fetching, updating (with the new GA fields), and deletion of a story, validating fields post-update. The logic appears sound, and negative cases (e.g., fetching a non-existent story) seem adequately handled elsewhere.


57-152: Comprehensive test scenario for StoryPage CRUD
This test exercises page creation, duplication, movement, and deletion, including checks for invalid page IDs. The coverage is extensive and verifies multiple page states.


154-206: Clear block creation and reordering checks
The test thoroughly validates adding, moving, and removing blocks, ensuring block IDs appear or disappear as expected. No issues found.


208-229: Property update assertions for blocks
This function verifies property setting and ensures correct reflection in subsequent fetches. It appears reliable and well-structured.


561-583: Schema alignment for updateStory mutation
The updated mutation signature and accompanying variables correctly incorporate enableGa and trackingId fields. This aligns with the new schema fields and is consistent with the test logic.


869-902: Removal logic handling
Removing a block now excludes the unused last parameter. The test confirms the block’s removal by checking the blocks array. Implementation looks straightforward and clear.

server/gql/storytelling.graphql (3)

23-24: Mandatory analytics fields
Declaring enableGa and trackingId as non-null is consistent with the requirement for always providing GA configuration in the schema. Confirm that all story creation paths supply valid defaults to avoid errors.


75-75: Comment section update
Renaming the comment section to # Publishment is minor and has no functional impact.


86-88: Optional analytics parameters in UpdateStoryInput
These new optional fields align with the mandatory fields in the Story type, allowing partial updates without forcing analytics changes each time. This addition is coherent with typical GraphQL input conventions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🔭 Outside diff range comments (3)
server/gql/plugin.graphql (1)

20-30: ⚠️ Potential issue

Add Google Analytics parameters to Story-related types.

According to the PR objectives, the Story component should support Google Analytics parameters (enableGa and trackingId). However, these parameters are not defined in the schema. Consider adding a new type or extending existing types to support GA functionality.

Here's a suggested implementation:

 enum PluginExtensionType {
   PRIMITIVE
   WIDGET
   BLOCK
   VISUALIZER
   INFOBOX
   Story
   StoryPage
   StoryBlock
   InfoboxBlock
 }

+type StoryGoogleAnalytics {
+  enableGa: Boolean!
+  trackingId: String!
+}
server/internal/usecase/interactor/asset.go (1)

154-182: 🛠️ Refactor suggestion

Enforce project permission checks in file import.
Similar to “Update,” if project != nil, consider verifying that the user has permission to write to that project within the same workspace to prevent unauthorized file imports.

server/pkg/layer/encoding/exporter.go (1)

37-42: ⚠️ Potential issue

Empty Encode method requires implementation.

The Encode method now returns nil without processing the merged layer. This could lead to unexpected behavior.

Please implement the encoding logic or document why encoding is skipped.

🧹 Nitpick comments (17)
server/e2e/gql_asset_test.go (2)

122-179: Comprehensive project association tests.
The new test covers various scenarios, including moving assets between projects and removing project associations. You might want to add negative-case testing (e.g., invalid project ID) to bolster coverage.


242-256: Asset removal logic.
The new function straightforwardly removes an asset. Consider adding a check for whether the asset ID belongs to the correct workspace or project if such constraints exist.

server/internal/adapter/gql/resolver_mutation_project.go (2)

178-182: Remove or clarify commented-out code
This block referencing pid2 is now commented out. If it's no longer needed, consider removing it to keep the codebase clean.


287-288: Typo in function name
ImporPluginFile might be a typo and should probably read ImportPluginFile.

- if err := usecases(ctx).Plugin.ImporPluginFile(ctx, pid, fileName, file); err != nil {
+ if err := usecases(ctx).Plugin.ImportPluginFile(ctx, pid, fileName, file); err != nil {
server/internal/usecase/interactor/scene.go (2)

711-713: Consider handling the error from url.Parse(urlStr).
Currently, the code discards the potential parsing error, which may cause unexpected behavior if urlStr is invalid. Handle or log the error explicitly to avoid silent failures.

- u, _ := url.Parse(urlStr)
+ u, parseErr := url.Parse(urlStr)
+ if parseErr != nil {
+   log.Warnfc(ctx, "Failed to parse URL: %v", parseErr)
+   continue
+ }

765-767: Consistent approach with asset addition to ZIP.
Similar to the earlier widget block, this repeats the same safe pattern of verifying the field type before invoking AddZipAsset. Consider using a more prominent log level than Info if failed asset additions are crucial.

server/internal/adapter/gql/gqlmodel/models_gen.go (2)

919-921: PropertyFieldLink new fields for dataset references.
The newly introduced dataset references can help in linking external data. Ensure that IDs referencing datasets are valid and handle the possibility of missing or partial dataset metadata.


1332-1334: Extended UpdateAssetInput to accept AssetID and ProjectID.
This addition allows assets to be reassigned to a different project or updated more flexibly. The approach appears coherent with the rest of the schema.

server/pkg/nlslayer/feature_test.go (1)

9-24: Consider renaming the test to match its current implementation.

Since this test no longer uses NewFeatureWithNewId and instead demonstrates the usage of NewFeature with an explicit ID, consider renaming it to better reflect its purpose, e.g., TestNewFeatureWithExplicitId.

-func TestNewFeatureWithNewId(t *testing.T) {
+func TestNewFeatureWithExplicitId(t *testing.T) {
server/internal/adapter/gql/gqlmodel/convert_asset.go (1)

12-16: Consider a more concise implementation using conditional expression.

The project ID handling can be simplified while maintaining the same functionality.

-	var pid *ID
-	if project := a.Project(); project != nil {
-		pidValue := IDFrom(*a.Project())
-		pid = &pidValue
-	}
+	pid := (*ID)(nil)
+	if project := a.Project(); project != nil {
+		id := IDFrom(*project)
+		pid = &id
+	}
server/pkg/scene/builder/scene.go (1)

23-24: Consider consistent naming between input parameters and struct fields.

The struct uses EnableGA and TrackingID while the input parameters use enableGa and trackingId. Consider aligning these names for better maintainability.

-	EnableGA          bool                    `json:"enableGa"`
-	TrackingID        string                  `json:"trackingId"`
+	EnableGa          bool                    `json:"enableGa"`
+	TrackingId        string                  `json:"trackingId"`
server/e2e/gql_user_test.go (1)

28-28: Consider documenting the unused parameter.

The gateway.File parameter is added for consistency but not used in the implementation.

Add a comment explaining why this parameter exists:

-func baseSeederUser(ctx context.Context, r *repo.Container, f gateway.File) error {
+// baseSeederUser creates test users and workspaces.
+// f parameter is added for consistency with other seeder functions but not used here.
+func baseSeederUser(ctx context.Context, r *repo.Container, f gateway.File) error {
server/internal/app/app.go (1)

164-168: Consider adding documentation for the dummy middleware.

The AuthMiddlewareDummy function is well-implemented but lacks documentation explaining its purpose and usage.

Add documentation to explain the function's purpose:

+// AuthMiddlewareDummy returns a middleware that bypasses authentication checks.
+// This is useful for development or testing environments where authentication is not required.
 func AuthMiddlewareDummy() (func(http.Handler) http.Handler, error) {
   return func(next http.Handler) http.Handler {
     return next
   }, nil
 }
server/e2e/common.go (3)

35-43: Consider using a more descriptive name for the global variable.

The variable name fr is not descriptive enough. Consider renaming it to fileRepo or globalFileRepo to better indicate its purpose.

-var fr *gateway.File
+var globalFileRepo *gateway.File

266-271: Add support for additional JSON types.

The JSONEqRegexpValue function only handles map[string]interface{} type. Consider adding support for arrays and other JSON types to make it more robust.

 func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
-	if actualData, ok := actual.Raw().(map[string]interface{}); ok {
+	switch actualData := actual.Raw().(type) {
+	case map[string]interface{}:
 		return JSONEqRegexpInterface(t, actualData, expected)
+	case []interface{}:
+		return JSONEqRegexpInterface(t, actualData, expected)
+	default:
+		t.Logf("Unsupported type for JSON comparison: %T", actual.Raw())
+		return false
 	}
-	return false
 }

283-297: Improve error handling in ValueDump.

The function silently ignores JSON marshaling errors. Consider logging these errors to help with debugging.

 func ValueDump(val *httpexpect.Value) {
 	raw := val.Raw()
 	switch data := raw.(type) {
 	case map[string]interface{}:
 		if text, err := json.MarshalIndent(data, "", "  "); err == nil {
 			fmt.Println(string(text))
+		} else {
+			fmt.Printf("Error marshaling map: %v\n", err)
 		}
 	case []interface{}:
 		if text, err := json.MarshalIndent(data, "", "  "); err == nil {
 			fmt.Println(string(text))
+		} else {
+			fmt.Printf("Error marshaling array: %v\n", err)
 		}
 	default:
 		fmt.Println("Unsupported type:", reflect.TypeOf(raw))
 	}
 }
server/internal/usecase/interactor/project.go (1)

513-533: Consider adding progress tracking for large exports.

For large projects with many assets, consider adding progress tracking to provide feedback during the export process.

 func (i *Project) ExportProjectData(ctx context.Context, projectID id.ProjectID, zipWriter *zip.Writer, operator *usecase.Operator) (*project.Project, error) {
+	var totalAssets int
+	var processedAssets int
+
 	prj, err := i.projectRepo.FindByID(ctx, projectID)
 	if err != nil {
 		return nil, errors.New("project " + err.Error())
 	}
 	if prj.IsDeleted() {
 		fmt.Printf("Error Deleted project: %v\n", prj.ID())
 		return nil, errors.New("This project is deleted")
 	}

 	// project image
 	if prj.ImageURL() != nil {
+		totalAssets++
 		err := AddZipAsset(ctx, i.file, zipWriter, prj.ImageURL().Path)
 		if err != nil {
 			return nil, err
 		}
+		processedAssets++
+		fmt.Printf("Export progress: %d/%d assets\n", processedAssets, totalAssets)
 	}

 	return prj, nil
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ad2fc9 and 7c10081.

⛔ Files ignored due to path filters (4)
  • server/e2e/test.zip is excluded by !**/*.zip
  • web/src/services/gql/__gen__/fragmentMatcher.json is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/gql.ts is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/graphql.ts is excluded by !**/__gen__/**
📒 Files selected for processing (107)
  • server/Makefile (3 hunks)
  • server/e2e/common.go (7 hunks)
  • server/e2e/dataset_export_test.go (0 hunks)
  • server/e2e/gql_asset_test.go (6 hunks)
  • server/e2e/gql_custom_property_test.go (1 hunks)
  • server/e2e/gql_layer_test.go (0 hunks)
  • server/e2e/gql_me_test.go (1 hunks)
  • server/e2e/gql_nlslayer_test.go (0 hunks)
  • server/e2e/gql_project_export_import_test.go (3 hunks)
  • server/e2e/gql_project_export_test.go (0 hunks)
  • server/e2e/gql_project_test.go (6 hunks)
  • server/e2e/gql_scene_test.go (4 hunks)
  • server/e2e/gql_storytelling_test.go (6 hunks)
  • server/e2e/gql_user_test.go (2 hunks)
  • server/e2e/gql_validate_geojson_test.go (1 hunks)
  • server/e2e/mock_test.go (1 hunks)
  • server/e2e/ping_test.go (1 hunks)
  • server/e2e/seeder.go (5 hunks)
  • server/go.mod (1 hunks)
  • server/gql/asset.graphql (5 hunks)
  • server/gql/cluster.graphql (0 hunks)
  • server/gql/dataset.graphql (0 hunks)
  • server/gql/layer.graphql (0 hunks)
  • server/gql/plugin.graphql (2 hunks)
  • server/gql/property.graphql (0 hunks)
  • server/gql/scene.graphql (1 hunks)
  • server/gql/storytelling.graphql (3 hunks)
  • server/gql/tag.graphql (0 hunks)
  • server/gql/workspace.graphql (2 hunks)
  • server/gqlgen.yml (0 hunks)
  • server/internal/adapter/gql/context.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/dataloader.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_layer.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go (5 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project.go (2 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_scene.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_tag.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (9 hunks)
  • server/internal/adapter/gql/loader.go (0 hunks)
  • server/internal/adapter/gql/loader_asset.go (2 hunks)
  • server/internal/adapter/gql/loader_dataset.go (0 hunks)
  • server/internal/adapter/gql/loader_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset_schema.go (0 hunks)
  • server/internal/adapter/gql/resolver_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_asset.go (3 hunks)
  • server/internal/adapter/gql/resolver_mutation_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (6 hunks)
  • server/internal/adapter/gql/resolver_mutation_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_storytelling.go (1 hunks)
  • server/internal/adapter/gql/resolver_mutation_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_property.go (0 hunks)
  • server/internal/adapter/gql/resolver_property_test.go (0 hunks)
  • server/internal/adapter/gql/resolver_query.go (1 hunks)
  • server/internal/adapter/gql/resolver_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_team.go (1 hunks)
  • server/internal/app/app.go (2 hunks)
  • server/internal/app/auth_client.go (1 hunks)
  • server/internal/infrastructure/fs/file.go (2 hunks)
  • server/internal/infrastructure/fs/file_test.go (3 hunks)
  • server/internal/infrastructure/memory/asset.go (1 hunks)
  • server/internal/infrastructure/mongo/asset.go (2 hunks)
  • server/internal/infrastructure/mongo/mongodoc/asset.go (3 hunks)
  • server/internal/infrastructure/mongo/mongodoc/scene.go (0 hunks)
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go (4 hunks)
  • server/internal/usecase/interactor/asset.go (6 hunks)
  • server/internal/usecase/interactor/layer.go (0 hunks)
  • server/internal/usecase/interactor/nlslayer.go (3 hunks)
  • server/internal/usecase/interactor/project.go (5 hunks)
  • server/internal/usecase/interactor/project_test.go (0 hunks)
  • server/internal/usecase/interactor/scene.go (5 hunks)
  • server/internal/usecase/interactor/scene_test.go (0 hunks)
  • server/internal/usecase/interactor/storytelling.go (1 hunks)
  • server/internal/usecase/interfaces/asset.go (2 hunks)
  • server/internal/usecase/interfaces/project.go (2 hunks)
  • server/internal/usecase/interfaces/story.go (1 hunks)
  • server/internal/usecase/repo/asset.go (1 hunks)
  • server/pkg/asset/asset.go (2 hunks)
  • server/pkg/asset/builder.go (1 hunks)
  • server/pkg/asset/id.go (1 hunks)
  • server/pkg/layer/encoding/exporter.go (1 hunks)
  • server/pkg/layer/layerops/processor_test.go (0 hunks)
  • server/pkg/nlslayer/feature.go (0 hunks)
  • server/pkg/nlslayer/feature_test.go (1 hunks)
  • server/pkg/scene/builder/builder.go (1 hunks)
  • server/pkg/scene/builder/builder_test.go (0 hunks)
  • server/pkg/scene/builder/encoder.go (1 hunks)
  • server/pkg/scene/builder/encoder_test.go (0 hunks)
  • server/pkg/scene/builder/scene.go (1 hunks)
  • server/pkg/scene/builder/story.go (1 hunks)
  • server/pkg/scene/builder_test.go (0 hunks)
  • server/pkg/storytelling/story.go (4 hunks)
  • server/pkg/storytelling/story_bulider.go (1 hunks)
  • web/src/services/api/propertyApi/utils.ts (1 hunks)
⛔ Files not processed due to max files limit (6)
  • web/src/services/api/sceneApi.ts
  • web/src/services/gql/fragments/dataset.ts
  • web/src/services/gql/fragments/index.ts
  • web/src/services/gql/fragments/layer.ts
  • web/src/services/gql/fragments/property.ts
  • web/src/services/gql/queries/scene.ts
💤 Files with no reviewable changes (47)
  • server/internal/adapter/gql/gqlmodel/convert.go
  • server/pkg/scene/builder_test.go
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go
  • server/internal/usecase/interactor/project_test.go
  • server/internal/adapter/gql/resolver_property_test.go
  • server/pkg/layer/layerops/processor_test.go
  • server/pkg/nlslayer/feature.go
  • server/internal/infrastructure/mongo/mongodoc/scene.go
  • server/internal/adapter/gql/gqlmodel/convert_scene.go
  • server/pkg/scene/builder/encoder_test.go
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go
  • server/e2e/dataset_export_test.go
  • server/e2e/gql_nlslayer_test.go
  • server/internal/adapter/gql/context.go
  • server/e2e/gql_layer_test.go
  • server/internal/adapter/gql/gqlmodel/models.go
  • server/e2e/gql_project_export_test.go
  • server/gql/property.graphql
  • server/internal/adapter/gql/resolver_mutation_scene.go
  • server/pkg/scene/builder/builder_test.go
  • server/internal/adapter/gql/gqlmodel/convert_tag.go
  • server/internal/adapter/gql/gqldataloader/dataloader.go
  • server/gqlgen.yml
  • server/gql/cluster.graphql
  • server/internal/adapter/gql/loader.go
  • server/internal/usecase/interactor/scene_test.go
  • server/internal/adapter/gql/resolver_mutation_dataset.go
  • server/internal/adapter/gql/gqlmodel/convert_layer.go
  • server/internal/adapter/gql/resolver_dataset.go
  • server/internal/adapter/gql/resolver_layer.go
  • server/internal/adapter/gql/resolver_scene.go
  • server/gql/dataset.graphql
  • server/internal/adapter/gql/resolver_mutation_tag.go
  • server/gql/tag.graphql
  • server/internal/adapter/gql/resolver_tag.go
  • server/internal/adapter/gql/loader_dataset.go
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go
  • server/internal/adapter/gql/resolver_mutation_layer.go
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go
  • server/internal/adapter/gql/resolver_dataset_schema.go
  • server/internal/adapter/gql/resolver_property.go
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go
  • server/gql/layer.graphql
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go
  • server/internal/usecase/interactor/layer.go
  • server/internal/adapter/gql/loader_tag.go
🧰 Additional context used
🧠 Learnings (2)
server/internal/adapter/gql/resolver_mutation_project.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:215-217
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project import functionality, the file names are unique and can be safely replaced uniformly using `bytes.Replace`.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:0-0
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project export functionality, the ZIP file is deleted after saving to storage.
server/e2e/gql_project_export_import_test.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:48-80
Timestamp: 2024-11-12T15:21:04.151Z
Learning: In `server/e2e/gql_import_export_test.go`, test functions like `TestCallImportProject` may have disabled assertions due to problems with GraphQL.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:12-44
Timestamp: 2024-11-12T15:21:04.150Z
Learning: The response body assertions in `TestCallExportProject` are disabled due to existing problems with GraphQL.
🔇 Additional comments (183)
server/gql/plugin.graphql (1)

99-99: LGTM!

The formatting changes improve readability.

Also applies to: 109-109

server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go (5)

11-19: LGTM! Improved feature creation with explicit ID generation.

The change from NewFeatureWithNewId to NewFeature with explicit ID generation provides better control and clarity over feature ID management.


21-29: LGTM! Consistent implementation of the new feature creation pattern.

The changes maintain consistency with the new explicit ID generation pattern.


31-39: LGTM! Consistent implementation maintained.

The changes follow the same explicit ID generation pattern consistently.


41-49: LGTM! Pattern consistency maintained.

The changes maintain the same explicit ID generation pattern.


51-62: LGTM! Pattern consistency maintained with proper geometry composition.

The changes maintain the same explicit ID generation pattern while preserving the correct geometry composition in the GeometryCollection.

server/internal/usecase/interactor/asset.go (2)

39-44: Consider validating project readability.
Currently, the code checks for readable workspaces but not specifically whether the user can read the project if pid is present. You might want to ensure operators have permission to read the project as well.


91-91: Association with project on creation.
Assigning the project ID here is consistent. Ensure that any external calls or references, if any, are also updated to handle the new project association.

server/internal/adapter/gql/resolver_query.go (1)

15-16: Confirm alignment with updated usecase method.
This new projectId parameter correctly threads through to the underlying data loader. It’s consistent with the asset interactor changes. Good job!

server/e2e/gql_asset_test.go (7)

15-15: No functional change.
This comment line is informational, and there’s nothing to address further.


17-120: Verify the coreSupport filtering logic.
At line 114, the test expects all returned assets to have coreSupport = true, but the setup also creates assets with coreSupport = false. Ensure the correct retrieval logic is invoked; otherwise, the test might pass or fail unexpectedly.


181-199: Updated GraphQL mutation.
Including projectId in the create mutation is aligned with the asset interactor changes. This addition seems correct.


200-222: File upload error handling.
Consider verifying the behavior when the file does not exist or the project ID is invalid. Handling these cases in tests can increase confidence in robustness.


224-241: Update mutation looks good.
The GraphQL layer to set (or remove) a project ID is consistent with the new interactor logic.


258-285: Programmatic file creation testing.
Creating temporary files for tests ensures the approach is realistic. Good approach for broad coverage.


287-350: Granular asset query.
The projectId filter here matches the updated schema requirements. This is a clean approach to ensure consistent test coverage of new arguments.

server/internal/adapter/gql/resolver_mutation_project.go (33)

10-12: New imports for URL and path operations
Adding "net/url" and "path" is appropriate for URL parsing and file path extraction.


130-130: Validate generated zip filename to prevent collisions
Using the project ID directly in the zip filename is generally fine, but consider sanitizing or validating the string to prevent collisions or invalid filenames.


155-155: Consistent rename to ExportProjectData
The call to ExportProjectData aligns with the revised function signature, indicating a clear purpose. Ensure all callers are updated accordingly.


183-183: Confirm performance for retrieving all project assets
FindByWorkspaceProject with a nil filter might lead to large asset sets. Ensure that this is intentional and does not impair performance.


189-194: Checks for potential filename collisions
Parsing the asset URL and inferring the filename is fine, but confirm whether multiple assets could produce identical filenames, overwriting each other in the assetNames map.


197-199: Straightforward use of data map
Storing project, plugins, and schema references in the main data map is clear and maintainable. No issues here.


212-212: New ImportProject function
This new mutation method is a logical addition, providing symmetrical functionality for export/import flows.


216-219: Validate ZIP content for security
When uncompressing user-provided files, ensure there is no risk of writing files outside the intended directory.


220-220: Consistent operator extraction
Storing the operator in a local variable keeps the pattern consistent and simplifies subsequent calls.


223-223: Explanatory comment
The comment clarifies why creating a project first is necessary. This is helpful context.


225-225: Robust approach for project import
Calling ImportProjectData to create a project from metadata is well-structured.


226-227: Good error handling
Returning a custom message ensures clarity for debugging.


229-231: Transactional approach
Safely rolling back if tx.End fails is good practice.


242-244: Runtime validation of project ID
Safely converting project IDs ensures consistent internal representation.


249-249: Map real asset name
Retrieving the real name from assetNames is correct, but be sure assetNames[beforeName] always exists to avoid empty strings.


252-254: ImportAssetFiles usage
The approach is consistent with the new import flow. Check for large or invalid files that might disrupt the system.


257-265: Updating project image
Conditionally updating the project's image is a sensible approach, and you handle errors properly.


267-268: Ensuring placeholders are replaced
Replacing the old filename with the new is correct for patching references in JSON. No obvious issues.


272-272: Scene creation for project
Creating a new scene post-import is a solid approach, ensuring references align with the newly imported project.


280-280: Parsing plugin file name
Splitting the path into parts is straightforward. Verify the array indices are always valid.


283-285: Proper plugin ID conversion
Converts the newly constructed plugin ID. This is correct.


292-292: Load plugin/scene data
Reading plugin & scene data in one go is convenient. No concerns here.


294-296: Importing plugins
Centralizing plugin imports is consistent. Investigate logs or metrics for large, time-consuming imports.


299-301: Import scene
Bringing scene data back in is well-named and methodical.


304-306: Import NLSLayers
Be mindful of potential conflicts if data references an existing layer with the same ID.


309-311: Import styles
Style import logic is consistent with the overall approach.


314-316: Import story
Supports the storytelling flow in the new project. Watch for null references in scene data.


319-319: Commit transaction
Explicitly committing the transaction after all subtasks is good practice.


334-334: End of ImportProject method
No additional logic here. Just the closing brace.


335-344: Scene ID replacement helper
replaceOldSceneID cleanly handles references to the old ID in JSON. The approach is straightforward and easy to maintain.


346-354: unmarshalProject
Extracts project from the JSON. Consider ensuring the top-level “project” key is always present before ignoring errors.


356-370: unmarshalAssets
Captures asset name mappings. Handling potential absent keys or invalid structures is recommended.


372-382: unmarshalPluginsScene
Cleanly organizes plugin, scene, and schema data. Watch for type assertions returning nil.

server/e2e/gql_project_export_import_test.go (25)

4-8: Additional imports for JSON and HTTP
Adding encoding/json, fmt, and net/http is expected for the new export/import test flow.


12-13: New references to id and idx packages
These appear to support ID validations within tests. Good for ensuring correctness.


19-19: Updated test run command
Comment clarifies how to run the new test. This is helpful.


21-21: Rename test to TestProjectExportImport
Reflects the consolidated coverage of both export and import in a single test.


23-23: Switching to fullSeeder
Enables a richer data context for the test. Slightly increases complexity but ensures thorough coverage.


25-29: getScene usage
Initial check on seeded data correctness. This ensures the baseline is as expected before export.


31-39: Exporting the data
exporProject function is introduced to test actual project data export, returning a filename. Straightforward approach.


41-42: Asserting project name & description
Checking the newly imported data's name and description ensures correct round-trip behavior.


44-44: Store new scene ID
Saving the newly returned scene ID for further validation is a logical step.


46-47: Retrieving imported scene
getScene is reused to confirm the newly created scene's correctness.


49-49: Structured approach to comparing results
Using compareValue repeatedly fosters granular checks for each property.


50-57: Compare scene properties
Examines multiple fields (styles, widgets, stories, newLayers, etc.) individually. This ensures thorough coverage.


58-63: Deferred cleanup of file
Removes the exported zip after test completion, avoiding clutter.


64-68: convertLine function
Flattens object fields into lines for segment-wise comparison. Straightforward.


71-102: compareValue function
Detailed line-by-line comparison helps isolate differences. The concept of ignoring id or propertyId is pragmatic.


103-106: isIgnore function
Allows skipping lines based on property name prefixes, maintaining test focus on other fields.


109-109: General ID check
isID discards lines if they are recognized as random IDs. This prevents spurious test failures.


112-119: UUID detection
Using idx.From[id.Widget] to verify the format is interesting. Confirm no side effects if ID is from another domain.


121-139: exporProject function
Performs the GraphQL mutation, downloads the ZIP, and writes it locally. The flow is a good representation of real usage.


141-141: importProject function signature
Returning *httpexpect.Value is consistent with the rest of the test’s pattern.


142-142: Opening the ZIP for upload
File handling is correct and includes defers for closing. No sign of concurrency issues.


169-169: Project data extraction
Validates that the importProject response includes projectData. This is the crux of the import test.


172-172: *Refactor getScene to return httpexpect.Value
Maintains test uniformity across retrieval methods.


182-182: Ensure node is present
v.NotNull() checks for a valid scene node, catching import failures.


185-185: Return the value
No further logic needed. Straightforward final step.

server/e2e/seeder.go (25)

5-9: New crypto/rand & JSON logic
Generating UUIDs and handling JSON content are typical steps for dynamic seeding.


11-12: OS file handling
Ensuring we have the correct file path and environment requirements for seeding.


15-16: Expanded references to usecase/gateway
Indicates heavier usage of the file gateway for uploading assets.


18-20: New usage of asset package
Creating assets directly within the seeder.


22-25: Adopting nlslayer & plugin references
Lays the foundation for more robust seeding with advanced features.


46-46: Project description appended
pDesc is updated to include the project name and "desc" suffix, clarifying the test project’s purpose.


49-49: Mocking a fixed time
now ensures consistent seeds across runs for repeatable e2e results.


51-52: Root layer initialization
These new IDs for root layers enable layering within scenes.


53-55: Story block entity IDs
IDs for story, page, and block are introduced to seed basic storytelling data.


58-58: baseSeeder signature changed
Now accepting gateway.File to handle asset uploads.


70-70: Refactoring baseSetup
Centralizes user and workspace creation along with asset usage.


73-73: Handling language support
baseSeederWithLang(..., lang) ensures we can seed data for multi-lingual tests.


88-88: Updated baseSetup
Moved logic for adding assets to a separate function. Improves clarity.


102-106: Create & store a default asset
Ensures the test project has at least one default image.


125-167: addAsset function
Opens the local file, detects MIME type, and uploads it with gateway.File. Then builds an asset domain object.


169-183: fullSeeder function
Extends baseSetup with additional layers, widgets, stories, etc. for more complex testing scenarios.


185-230: fullSetup
Incorporates the root layer, property creation, plugin associations, widget additions, storytelling, and more.


232-281: addWidget
Creates a sample widget from the official plugin. Logic checks for extended mode, location, etc.


283-312: addStory
Builds a property for a story, creates a single page, and saves it. This is the seed for a multi-page story scenario.


314-338: _createPage
Establishes a minimal page with one block, suitable for nested storytelling.


340-368: _createBlock
Constructs a textual story block from the "textStoryBlock" extension. Good error handling for plugin retrieval.


370-393: addLayerStyle
Generates a style property and saves it, laying ground for advanced layer styling in scenes.


395-448: addLayerSimple
Adds a simple layer, which can hold sketches and configurations.


450-510: addGeoJson
Imports geometry data from JSON, transforms it into a feature, and updates the existing simple layer’s sketch info.


512-527: generateUUID
Creates a safe, collision-resistant UUID for seeding objects.

server/internal/usecase/interactor/scene.go (3)

22-22: New storytelling import looks appropriate.
The newly introduced import from storytelling aligns with the usage of story-related logic in this file.


680-683: Safe nil-check for storyList but only the first story is used.
This conditional ensures that storyList has at least one entry before dereferencing. Confirm that only handling the first story is intentional, as any additional stories will be ignored.


740-741: Type-check for URL usage is valid.
This conditional elegantly handles fields that may or may not be URLs. Proceeding only when the data is indeed a *url.URL avoids potential type mismatches.

server/e2e/gql_storytelling_test.go (5)

15-55: TestStoryCRUD ensures story creation, updating, and deletion.
These tests verify initial story count, creation/update of a story (including GA fields), and final deletion. This structure looks robust and covers key mutations and queries.


57-152: TestStoryPageCRUD thoroughly checks page creation, duplication, moves, and deletions.
This suite demonstrates comprehensive coverage of CRUD operations on pages, including boundary cases such as invalid page IDs, as well as verifying updated order of pages.


154-207: TestStoryPageBlocksCRUD validates block creation, movement, and removal.
These assertions confirm that blocks can be dynamically inserted and reordered, ensuring the page structure updates as expected.


208-229: TestStoryPageBlocksProperties confirms property updates on blocks.
This ensures property fields (e.g., text, padding) can be updated correctly and then properly retrieved. Coverage is good for typical property fields.


231-413: TestStoryPublishing checks final publishing workflow and data retrieval.
The test publishes a story and verifies the serialized content (including GA defaults) from both internal read calls and external endpoint checks. This comprehensively validates the final publishing flow.

server/internal/adapter/gql/gqlmodel/models_gen.go (4)

581-588: Expanded fields in MergedPropertyField.
The structure now references SchemaID, FieldID, and so on. The approach of storing both value and type details in a single struct is consistent. Just ensure that all references (Schema, Field, etc.) remain synchronized in usage.
[approve]


906-915: PropertyField captures more detailed metadata.
This expansion for PropertyField (adding Links, Type, Value, etc.) provides deeper control over property data. Validate that the new references to parent objects are never nil in usage.


1221-1222: New GA fields in Story.
The addition of EnableGa and TrackingID fits the PR’s objective of integrating Google Analytics. Implementation looks consistent with usage in the tests.


1337-1339: Matching UpdateAssetPayload changes to reflect new project association.
The payload now echoes updated AssetID and ProjectID, ensuring clients can confirm changes to the asset’s project linkage.

server/e2e/gql_me_test.go (1)

8-8:

❓ Verification inconclusive

Verify the impact of simplified server initialization.

The server initialization has been simplified, but we need to ensure that the new Server function maintains the necessary configuration for authentication in tests.


🏁 Script executed:

#!/bin/bash
# Search for Server function definition to verify auth config
ast-grep --pattern 'func Server($_, $_) {
  $$$
}'

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Search for the definition of the Server function along with its surrounding context 
# to manually inspect if authentication configuration is maintained.
rg -n "func Server(" .

Length of output: 99


Review and verify Server initialization for auth requirements.

The change simplifies server startup by switching from StartServer to Server. Unfortunately, our automated checks couldn’t retrieve the Server function’s implementation details. Please manually verify that the new Server function still includes the necessary authentication configuration for the tests.

server/e2e/ping_test.go (1)

9-9:

❓ Verification inconclusive

Verify CORS configuration in ServerPingTest.

The server initialization has been simplified to ServerPingTest. Ensure that CORS settings are properly configured for the test environment.


🏁 Script executed:

#!/bin/bash
# Search for ServerPingTest function definition to verify CORS config
ast-grep --pattern 'func ServerPingTest($_, $_) {
  $$$
}'

Length of output: 60


Attention: Verify CORS configuration in ServerPingTest Initialization

The server initialization has been simplified to use ServerPingTest, and it’s crucial to ensure that the test environment still applies the expected CORS settings. Please manually verify that:

  • CORS-related configurations (e.g., headers or middleware) are explicitly set within or prior to invoking ServerPingTest.
  • The behavior aligns with similar configurations in other parts of the codebase (like gql_me_test.go).

🏁 Script executed:

#!/bin/bash
# Enhanced search for the definition of ServerPingTest in Go files,
# ignoring parameter details to capture all variations.
rg -n "func[[:space:]]\+ServerPingTest" -t go .

# Additionally, search within the located files for any CORS configuration or keywords.
rg -n "CORS" -t go .

Length of output: 154


Verify that CORS settings are correctly applied in ServerPingTest initialization

It appears that the test server is now initialized via ServerPingTest (as seen in server/e2e/ping_test.go), but our search did not reveal the function’s definition directly. We did observe a CORS configuration in ./server/internal/app/app.go (line 54), which suggests that CORS is set up at the application level. Please verify that:

  • ServerPingTest Initialization: Either directly applies the expected CORS middleware or incorporates the standard app initialization that includes CORS.
  • CORS Consistency: The configuration applied here aligns with what’s done in similar tests (e.g., in gql_me_test.go) and ensures that CORS headers are present during testing.

Manually confirm that the test environment reflects these settings or update the initialization if necessary.

server/internal/usecase/repo/asset.go (2)

1-1: Verify alignment with PR objectives.

The changes in these files appear to be refactoring-related and don't directly contribute to the stated objective of integrating Google Analytics parameters into the Story component. Consider splitting these changes into a separate PR to maintain focus on the GA integration.


20-20:

❓ Verification inconclusive

Verify all callers are updated for the new method signature.

The method has been renamed from FindByWorkspace to FindByWorkspaceProject with an additional ProjectID parameter. This is a breaking change.


🏁 Script executed:

#!/bin/bash
# Search for FindByWorkspace usage in the codebase
rg "FindByWorkspace\(" --type go

# Search for FindByWorkspaceProject usage to verify migration
rg "FindByWorkspaceProject\(" --type go

Length of output: 4963


Ensure All Call Sites Properly Delegate to the Updated Asset Method

The asset repository method in server/internal/usecase/repo/asset.go has been renamed from FindByWorkspace to FindByWorkspaceProject and now requires an additional ProjectID parameter. Please verify that:

  • All direct calls to the asset repository (e.g. in the interactor, and in the infrastructure layers such as the memory and mongo implementations) now correctly use or delegate to FindByWorkspaceProject.
  • The GraphQL adapter/wrapper in server/internal/adapter/gql/loader_asset.go correctly calls c.usecase.FindByWorkspaceProject even if the externally exposed method remains named FindByWorkspace—ensuring no accidental bypass of the new signature.
  • Test files (e.g. server/e2e/gql_asset_test.go) and all other callers have been updated to prevent any mismatch caused by the breaking signature change.

Once you’ve confirmed that all these integrations and delegations are working as expected, this change will be safe to merge.

server/pkg/asset/id.go (1)

10-10: LGTM! Well-structured additions for project ID handling.

The new ProjectID type and its associated helper functions follow the established pattern, maintaining consistency with existing ID types.

Also applies to: 14-14, 18-18, 22-22, 26-26

server/e2e/mock_test.go (1)

11-11: LGTM! Good refactoring to use shared server initialization.

The change simplifies the test setup by using ServerMockTest, reducing code duplication while maintaining the same test behavior.

server/internal/adapter/gql/gqlmodel/convert_asset.go (1)

12-16: LGTM! Proper handling of project ID association.

The implementation correctly handles nil checks and pointer conversions for the project ID.

server/pkg/asset/asset.go (2)

18-18: LGTM! The new field follows Go best practices.

The project field is properly encapsulated as a private field with a pointer type, allowing for optional project associations.


34-40: LGTM! The getter and setter methods are well-implemented.

The methods follow Go's idiomatic approach to encapsulation and provide a clean interface for accessing and modifying the project field.

server/internal/usecase/interfaces/asset.go (2)

28-28: LGTM! The new field is properly typed.

The ProjectID field is correctly defined as a pointer type, making it optional and consistent with the project association feature.


39-39: LGTM! Method signatures are clear and consistent.

The updated method signatures properly handle project associations:

  • FindByWorkspaceProject clearly indicates its enhanced functionality
  • Update method includes project ID modification
  • ImportAssetFiles consistently includes project ID parameter

Also applies to: 41-41, 43-43

server/internal/adapter/gql/resolver_team.go (1)

27-29: LGTM! The resolver update is consistent with the project association feature.

The Assets method signature and loader call are properly updated to include the project ID parameter while maintaining existing pagination functionality.

server/pkg/asset/builder.go (1)

57-60: LGTM! Project field addition looks good.

The implementation of the Project method in the Builder follows the established pattern of other builder methods, maintaining consistency in the codebase.

server/internal/infrastructure/mongo/mongodoc/asset.go (3)

17-17: LGTM! Project field addition to AssetDocument.

The optional Project field is correctly defined as *string to handle null values in MongoDB.


36-40: LGTM! Project ID conversion in NewAsset.

The conversion of Project ID to string is properly handled with null checks.


65-72: LGTM! Project ID parsing in Model method.

The conversion from string to ProjectID is properly handled with error checking.

server/internal/adapter/gql/resolver_mutation_asset.go (2)

19-26: LGTM! Project ID handling in CreateAsset.

The conversion of GraphQL input to internal ProjectID is properly handled with error checking.


41-62: LGTM! UpdateAsset mutation implementation.

The UpdateAsset mutation correctly handles the conversion of asset and project IDs with proper error handling.

server/pkg/scene/builder/story.go (2)

45-45: LGTM! Title field addition to storyJSON.

The Title field is correctly added to the JSON output.


1-84: Inconsistency between PR objectives and actual changes.

The PR objectives state that this PR adds Google Analytics support to the Story component by adding enableGa and trackingId fields. However, the actual changes in the code are about:

  1. Adding Project field support to Assets
  2. Adding Title field to storyJSON

This suggests that either:

  • The wrong PR description was provided
  • The wrong files were provided for review
  • The GA-related changes are missing

Likely an incorrect or invalid review comment.

server/pkg/storytelling/story_bulider.go (1)

124-132: LGTM! Clean implementation of GA support.

The new builder methods for Google Analytics support are well-implemented, following the existing builder pattern consistently.

server/internal/usecase/interfaces/project.go (2)

46-47: LGTM! GA fields properly added to UpdateProjectParam.

The GA fields are correctly defined as nullable pointers, allowing for optional updates.


74-75: LGTM! Method renames and type safety improvements.

The method renames improve clarity, and the use of idx.ID[accountdomain.Workspace] enhances type safety.

server/internal/adapter/gql/loader_asset.go (2)

39-55: LGTM! Project ID filtering properly implemented.

The project ID handling is well-implemented with proper null checks and error handling.


60-66: Performance improvement in edge creation.

Pre-allocating the edges slice with the known size is a good optimization.

server/internal/infrastructure/memory/asset.go (2)

49-59: LGTM! Project filtering properly integrated.

The project ID filtering is well-integrated with the existing workspace filtering logic, maintaining backward compatibility.


55-57:

❓ Verification inconclusive

Consider adding a test for project filtering.

While the implementation looks correct, it would be good to add unit tests specifically for the project filtering logic.


🏁 Script executed:

#!/bin/bash
# Check if there are any tests for project filtering
rg -l "TestFindByWorkspaceProject" server/internal/infrastructure/memory/

Length of output: 73


Action Required: Add or Verify Unit Tests for Project Filtering

The project filtering logic in server/internal/infrastructure/memory/asset.go (lines 55–57) currently lacks dedicated tests. A search for a test named “TestFindByWorkspaceProject” returned no results, so please verify manually if tests for project filtering exist elsewhere. If not, it's advisable to add unit tests to specifically cover cases such as:

  • Handling non-nil project IDs and nil projects.
  • Verifying matching against provided project IDs.
  • Confirming the behavior when a keyword filter is applied.
server/e2e/gql_scene_test.go (2)

16-16: LGTM! Improved response object access pattern.

The changes to use .Object().Value() make the code more robust by ensuring proper type checking at each level of the response object.

Also applies to: 42-42, 71-73


77-100: LGTM! Well-structured project creation function.

The new createProjectWithExternalImage function is well-implemented with:

  • Clear GraphQL mutation structure
  • Proper error handling
  • Type-safe response handling
server/internal/adapter/gql/gqlmodel/convert_project.go (2)

108-113: LGTM! Well-structured export type.

The ProjectExport type is well-designed with:

  • Appropriate field types
  • Clear JSON tags
  • Optional URL handling

115-137: LGTM! Robust conversion functions.

Both ToProjectExport and ToProjectExportFromJSON functions include:

  • Proper nil checks
  • Clean error handling
  • Type-safe conversions
server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1)

35-36: LGTM! Successfully integrated GA support.

The addition of EnableGa and TrackingID fields properly implements the PR objective of adding Google Analytics support to the Story component.

server/e2e/gql_validate_geojson_test.go (1)

186-186: LGTM! Updated function call with project ID parameter.

The addition of the nil project ID parameter aligns with the enhanced asset management capabilities while maintaining existing test functionality.

server/internal/usecase/interfaces/story.go (1)

38-39: LGTM! Google Analytics fields added correctly.

The new fields EnableGa and TrackingID are properly implemented as optional fields using pointers, which aligns with the existing pattern for update operations.

server/pkg/scene/builder/builder.go (1)

181-181: LGTM! Google Analytics parameters correctly propagated.

The GA parameters are properly passed through to the sceneJSON method, maintaining the flow of configuration data.

server/internal/infrastructure/mongo/asset.go (1)

67-101: LGTM! Project-based filtering properly implemented.

The method has been correctly updated to support filtering by both workspace and project ID. The filter construction is well-structured with proper handling of both cases.

server/internal/app/auth_client.go (2)

105-107: LGTM! Improved security by filtering sensitive logs.

The condition prevents logging sensitive user information during e2e tests.


115-117: LGTM! Consistent filtering of operator logs.

The condition maintains consistency with the user log filtering above.

server/pkg/storytelling/story.go (1)

58-60: LGTM! Added nil check to prevent panic.

The nil check in Pages() method improves robustness.

server/internal/infrastructure/mongo/mongodoc/storytelling.go (4)

34-35: LGTM! Added Google Analytics fields.

The fields match the Story struct definition.


87-88: LGTM! Consistent field initialization.

The GA fields are properly initialized from the Story model.


197-198: LGTM! Proper field mapping in Model method.

The GA fields are correctly mapped when building the Story model.


226-231: LGTM! Improved variable naming.

Renamed variable from page to block for better clarity.

server/internal/app/app.go (1)

72-78: LGTM! Authentication handling is well-structured.

The changes introduce a flexible authentication mechanism that allows for a mock authentication path when the authentication server is disabled, while maintaining the original authentication flow when enabled.

server/internal/infrastructure/fs/file.go (2)

113-113: LGTM! Path validation is secure and accurate.

The path validation logic correctly checks that the asset path is within the expected directory structure.


286-286: LGTM! URL path construction is simplified.

The URL path construction is simplified while maintaining the correct path structure.

web/src/services/api/propertyApi/utils.ts (1)

247-248:

❓ Verification inconclusive

Verify the impact of removing actualValue.

The removal of actualValue from the merged property field type might affect code that relies on this field.

Run the following script to check for any remaining usages of actualValue:


🏁 Script executed:

#!/bin/bash
# Description: Check for any remaining usages of `actualValue` in the codebase.

# Test: Search for any remaining usages of `actualValue`. Expect: No occurrences.
rg -A 5 $'actualValue'

Length of output: 13864


Action Required: Confirm No Unintended Impact from Removing actualValue

The removal of actualValue from the merged property field type in web/src/services/api/propertyApi/utils.ts appears correct. However, the codebase still contains several references to actualValue that may indicate related functionality:

  • GraphQL Fragments:

    • Files like web/src/services/gql/__gen__/gql.ts and web/src/services/gql/fragments/property.ts still show a commented-out # actualValue. Verify that this auto-generated/commented code does not cause any discrepancies with the current schema expectations.
  • Server Configuration:

    • In server/gqlgen.yml, actualValue is still configured with resolver: true. Confirm whether the GraphQL schema and resolvers need updating to reflect the removal.
  • Testing and Utility Code:

    • In test files (e.g., server/internal/infrastructure/mongo/mongodoc/nlslayer_test.go) and the flag generation code (server/tools/cmd/gen/flag.go), there are usages of actualValue (function calls and variable assignments). Please double-check if these usages are still necessary or if they should be refactored in line with the change.

Please verify these areas to ensure no dependent functionality is inadvertently broken.

server/internal/infrastructure/fs/file_test.go (3)

116-117: LGTM! Test case updated to match new URL handling.

The base URL has been correctly updated to use the root path, aligning with the changes in the main code.


165-165: LGTM! Base URL updated consistently.

The base URL has been consistently updated across test cases.


316-316: LGTM! Test case updated for URL path construction.

The test case has been updated to reflect the simplified URL path construction.

server/e2e/gql_custom_property_test.go (1)

347-762: LGTM! Query structure improvements.

The GraphQL query has been significantly improved:

  • Better organization using fragments
  • Clear separation of concerns
  • Improved readability with proper indentation
  • Comprehensive coverage of all required fields
server/e2e/gql_project_test.go (3)

144-144:

✅ Verification successful

Verify the test expectation change.

The test expectation for the number of edges has been increased from 4 to 5. Ensure this change aligns with the project creation calls in the test.


🏁 Script executed:

#!/bin/bash
# Description: Check the project creation calls in the test.

# Search for project creation calls in the test
rg -A 5 "createProject\(" server/e2e/gql_project_test.go

Length of output: 2566


Test Expectation Verified: 5 Edges Expected

The test now creates 5 projects (as verified by the 5 distinct createProject calls), so updating the edge length check to edges.Length().IsEqual(5) aligns with the current test setup. Please ensure that any future modifications to project creation maintain this consistent expectation.


585-585:

❓ Verification inconclusive

Verify the test expectation changes.

Multiple test expectations have been updated:

  • Total count changed from 20 to 21
  • Total count changed from 4 to 5
  • Expected edges length changed from 4 to 5

Ensure these changes align with the test data setup.

Also applies to: 617-617, 620-620


🏁 Script executed:

#!/bin/bash
# Description: Check the test data setup to verify the expected counts.

# Search for project creation in test data setup
rg -A 5 "projects\(t, ctx, r, " server/e2e/gql_project_test.go

Length of output: 813


Action: Confirm Updated Test Expectations Against Data Setup

The test now asserts updated values:

  • The total project count expectation was modified from 20 to 21.
  • Other expectations were adjusted (total count from 4 to 5 and edges length from 4 to 5).

Please ensure these updated assertions properly reflect the intended test data. For instance, the grep output in server/e2e/gql_project_test.go still shows calls like:

  • projects(t, ctx, r, 20, wID, "[wID]project", "ALIAS1", true)
  • And similar invocations with a hardcoded count (e.g., 10)

Verify that an additional project (or related change) has been intentionally introduced so that expecting 21 projects (and the corresponding count/edge changes on lines 617 and 620) is correct. If the intention is to adjust the test expectations, please confirm all parts of the test—in both data setup and assertions—are updated accordingly.


42-42:

✅ Verification successful

Verify the test expectation change.

The test expectation for the number of edges has been increased from 1 to 2. Ensure this change aligns with the actual data setup in the test.


🏁 Script executed:

#!/bin/bash
# Description: Check the test data setup to verify the expected number of edges.

# Search for project creation calls in the test
rg -A 5 "createProject\(" server/e2e/gql_project_test.go

Length of output: 2566


🏁 Script executed:

#!/bin/bash
# Display lines around the assertion at line 42 to provide additional context.
sed -n '40,44p' server/e2e/gql_project_test.go

Length of output: 181


Test Expectation Change Verified

The assertion at line 42 now checks that edges.Length() equals 2, which is consistent with the test data setup. In this test, only two projects are marked (via the starred calls) to be included in the query results, so the updated expectation appropriately reflects the changes. Please ensure that any future modifications to the test data or filtering logic also consider this expected count.

server/internal/usecase/interactor/storytelling.go (1)

188-194: LGTM! Google Analytics support added correctly.

The implementation adds support for enabling/disabling Google Analytics and setting the tracking ID. The changes are well-structured and maintain consistency with the existing codebase.

server/internal/usecase/interactor/nlslayer.go (2)

859-864: LGTM! Improved feature creation with explicit ID.

The change uses NewFeature with an explicit ID creation, which is a better approach than the previous implementation.


1109-1134: LGTM! Enhanced feature collection handling.

The changes improve the handling of feature collections during import by:

  • Using clearer variable names
  • Properly handling feature properties
  • Maintaining better code structure
server/gql/scene.graphql (1)

50-50: LGTM! Improved formatting.

The change adds a space after the Query keyword, improving readability while maintaining functionality.

server/gql/asset.graphql (6)

5-5: LGTM! The projectId field is correctly added to the Asset type.

The optional projectId field allows assets to be associated with projects, enhancing asset organization.


24-24: LGTM! The projectId field is correctly added to the CreateAssetInput.

The optional projectId field in the input type maintains consistency with the Asset type.


29-32: LGTM! The UpdateAssetInput type is well-defined.

The input type correctly includes the required assetId and optional projectId fields.


49-52: LGTM! The UpdateAssetPayload type is well-structured.

The payload type correctly mirrors the input fields, providing a consistent response structure.


75-75: LGTM! The projectId parameter is correctly added to the assets query.

The optional parameter allows filtering assets by project.


84-84: LGTM! The updateAsset mutation is correctly defined.

The mutation is properly integrated into the schema with appropriate input and payload types.

server/gql/workspace.graphql (3)

8-14: LGTM! The assets method signature is well-structured.

The method now includes the optional projectId parameter and has improved readability with parameters on separate lines.


15-21: LGTM! The projects method signature is well-formatted.

The method's parameters are now properly formatted on separate lines, improving readability.


121-123: LGTM! The removeMemberFromTeam mutation is well-formatted.

The mutation's input parameter is now properly formatted on a separate line.

server/gql/storytelling.graphql (2)

23-24: LGTM! Google Analytics fields are correctly added to the Story type.

The required fields enableGa and trackingId are properly defined with appropriate types.


86-89:

❓ Verification inconclusive

LGTM! Google Analytics fields are correctly added to UpdateStoryInput.

The optional fields enableGa and trackingId are properly defined under a clear comment section.

Let's verify that the tracking ID format is valid when provided:


🏁 Script executed:

#!/bin/bash
# Search for validation of Google Analytics tracking ID format
rg -A 5 'trackingId.*String'

Length of output: 25485


Google Analytics Fields Update – Review & Suggestion

The GraphQL schema in server/gql/storytelling.graphql correctly adds the optional enableGa and trackingId fields under a clear comment. A repository-wide search confirms that these fields are uniformly declared as String (or String! in some contexts) without any additional format validation logic for trackingId.

  • The tracking ID is defined simply as a string in multiple locations (e.g., in storytelling.graphql, project.graphql, and generated Go files) without regex-based or similar validations.
  • If enforcing a specific Google Analytics tracking ID format (e.g., "UA-XXXXXX-X") is desired, please consider implementing the appropriate validation either in the schema or within the resolver/input processing logic.

Overall, the changes are LGTM, with a follow-up to verify whether additional format validation is necessary.

server/go.mod (1)

133-133: LGTM! The dependency is correctly marked as indirect.

The github.com/oklog/ulid package is appropriately marked as an indirect dependency.

server/internal/adapter/gql/resolver_mutation_storytelling.go (1)

58-59: LGTM! Google Analytics fields added correctly.

The addition of EnableGa and TrackingID fields to the UpdateStoryInput aligns with the PR objectives and follows the existing code structure.

server/internal/usecase/interactor/project.go (1)

627-649: LGTM! Well-structured helper function.

The AddZipAsset function is well-implemented with proper error handling and resource cleanup. The function:

  • Handles external URLs gracefully
  • Uses defer for proper cleanup
  • Has clear error messages

@hexaforce hexaforce force-pushed the feat/storytelling-ga-support branch from 7c10081 to ddf2e3c Compare February 14, 2025 08:54
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (4)
server/internal/usecase/interactor/asset.go (1)

108-127: ⚠️ Potential issue

Validate project access permissions in Update method.

The Update method should verify that the operator has permission to associate the asset with the new project.

Apply this diff to add project access validation:

 func (i *Asset) Update(ctx context.Context, aid id.AssetID, pid *id.ProjectID, operator *usecase.Operator) (id.AssetID, *id.ProjectID, error) {
     return Run2(
         ctx, operator, i.repos,
         Usecase().Transaction(),
         func(ctx context.Context) (id.AssetID, *id.ProjectID, error) {
             asset, err := i.repos.Asset.FindByID(ctx, aid)
             if err != nil {
                 return aid, pid, err
             }

             if ok := operator.IsWritableWorkspace(asset.Workspace()); !ok {
                 return aid, pid, interfaces.ErrOperationDenied
             }

+            // Validate project access
+            if pid != nil {
+                project, err := i.repos.Project.FindByID(ctx, *pid)
+                if err != nil {
+                    return aid, pid, err
+                }
+                if project == nil || project.Workspace() != asset.Workspace() {
+                    return aid, pid, interfaces.ErrOperationDenied
+                }
+                if ok := operator.IsWritableWorkspace(project.Workspace()); !ok {
+                    return aid, pid, interfaces.ErrOperationDenied
+                }
+            }
+
             asset.SetProject(pid)

             return aid, pid, i.repos.Asset.Save(ctx, asset)
         },
     )
 }
server/e2e/common.go (1)

266-271: 🛠️ Refactor suggestion

Add error handling for type assertion in JSONEqRegexpValue.

The type assertion should handle failure cases more gracefully.

Apply this diff to improve error handling:

 func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
-    if actualData, ok := actual.Raw().(map[string]interface{}); ok {
-        return JSONEqRegexpInterface(t, actualData, expected)
-    }
-    return false
+    actualData, ok := actual.Raw().(map[string]interface{})
+    if !ok {
+        t.Errorf("expected map[string]interface{}, got %T", actual.Raw())
+        return false
+    }
+    return JSONEqRegexpInterface(t, actualData, expected)
 }
server/pkg/storytelling/story.go (2)

134-136: 🛠️ Refactor suggestion

Add validation for enableGa and trackingID combination.

When enableGa is true, trackingID should be required to ensure proper Google Analytics configuration.

func (s *Story) SetEnableGa(enableGa bool) error {
+       if enableGa && s.trackingID == "" {
+               return fmt.Errorf("tracking ID is required when Google Analytics is enabled")
+       }
        s.enableGa = enableGa
+       return nil
}

138-140: 🛠️ Refactor suggestion

Add validation for Google Analytics tracking ID format.

The tracking ID should follow Google Analytics format (UA-XXXXX-Y for Universal Analytics or G-XXXXXX for GA4).

func (s *Story) SetTrackingID(trackingID string) error {
+       // Validate GA4 (G-XXXXXX) or Universal Analytics (UA-XXXXX-Y) format
+       if trackingID != "" && !regexp.MustCompile(`^(UA-\d{4,10}-\d{1,4}|G-[A-Z0-9]+)$`).MatchString(trackingID) {
+               return fmt.Errorf("invalid Google Analytics tracking ID format")
+       }
        s.trackingID = trackingID
+       return nil
}
🧹 Nitpick comments (12)
server/pkg/scene/builder/builder.go (1)

128-163: Enhance error messages for better debugging.

While the error messages have been improved, they could be more descriptive by including the actual error details.

Apply this diff to improve error messages:

-			return nil, errors.New("Fail buildScene :" + err.Error())
+			return nil, fmt.Errorf("failed to build scene: %w", err)
-			return nil, errors.New("Fail buildStory :" + err.Error())
+			return nil, fmt.Errorf("failed to build story: %w", err)
-			return nil, errors.New("Fail buildNLSLayers :" + err.Error())
+			return nil, fmt.Errorf("failed to build NLS layers: %w", err)
-			return nil, errors.New("Fail buildLayerStyles :" + err.Error())
+			return nil, fmt.Errorf("failed to build layer styles: %w", err)

This change:

  1. Uses fmt.Errorf for better error wrapping
  2. Improves error message formatting
  3. Uses lowercase for error messages as per Go conventions
  4. Preserves error chain using %w verb
server/e2e/gql_storytelling_test.go (1)

869-904: Consider simplifying the function signature.

The removeBlock function returns a string that isn't used in the test assertions. Consider simplifying the return signature to just (GraphQLRequest, *httpexpect.Value) for better maintainability.

-func removeBlock(e *httpexpect.Expect, storyID, pageID, blockID string) (GraphQLRequest, *httpexpect.Value, string) {
+func removeBlock(e *httpexpect.Expect, storyID, pageID, blockID string) (GraphQLRequest, *httpexpect.Value) {
     // ... function body ...
-    return requestBody, res, res.Path("$.data.removeStoryBlock.blockId").Raw().(string)
+    return requestBody, res
}
server/internal/adapter/gql/gqlmodel/convert_project.go (3)

108-113: Add documentation for the new type.

The ProjectExport type is well-structured, but would benefit from documentation explaining its purpose and usage.

Add a documentation comment:

+// ProjectExport represents the essential project data for export/import operations.
+// It contains a subset of Project fields that are relevant for project templates.
 type ProjectExport struct {

115-125: Add documentation for the conversion function.

The ToProjectExport function is well-implemented and follows the established conversion patterns in the codebase.

Add a documentation comment:

+// ToProjectExport converts a project.Project domain model to a ProjectExport model.
+// Returns nil if the input project is nil.
 func ToProjectExport(p *project.Project) *ProjectExport {

127-137: Optimize JSON conversion for better performance.

The current implementation performs unnecessary JSON marshal/unmarshal operations. Consider using direct field mapping or JSON decoder for better performance.

Here's a more efficient implementation:

 func ToProjectExportFromJSON(data map[string]any) *ProjectExport {
-    var p ProjectExport
-    bytes, err := json.MarshalIndent(data, "", "  ")
-    if err != nil {
-        return nil
-    }
-    if err := json.Unmarshal(bytes, &p); err != nil {
-        return nil
-    }
-    return &p
+    if data == nil {
+        return nil
+    }
+    
+    p := &ProjectExport{
+        Name:        toString(data["name"]),
+        Description: toString(data["description"]),
+    }
+    
+    if v, ok := data["visualizer"].(string); ok {
+        p.Visualizer = Visualizer(v)
+    }
+    
+    if v, ok := data["imageUrl"].(string); ok {
+        if u, err := url.Parse(v); err == nil {
+            p.ImageURL = u
+        }
+    }
+    
+    return p
 }
+
+func toString(v any) string {
+    if s, ok := v.(string); ok {
+        return s
+    }
+    return ""
+}
server/e2e/gql_project_export_import_test.go (3)

58-61: Consider using t.Cleanup for file cleanup.

Instead of using a deferred function for cleanup, consider using t.Cleanup which is more idiomatic for tests and ensures cleanup even if the test fails.

-defer func() {
-  err := os.Remove(fileName)
-  assert.Nil(t, err)
-}()
+t.Cleanup(func() {
+  err := os.Remove(fileName)
+  assert.Nil(t, err)
+})

103-119: Add documentation for helper functions.

The helper functions isIgnore and isID lack documentation explaining their purpose and expected behavior.

Add documentation comments:

+// isIgnore checks if two lines should be ignored in comparison based on specific property names
 func isIgnore(propertyName, expectedLine, actualLine string) bool {

+// isID checks if the given text represents a valid widget ID
 func isID(text string) bool {

172-186: Add retry mechanism for flaky GraphQL queries.

Based on the retrieved learnings about problems with GraphQL, consider adding a retry mechanism for the getScene function.

+func getSceneWithRetry(e *httpexpect.Expect, s string, l string, maxRetries int) *httpexpect.Value {
+  var lastErr error
+  for i := 0; i < maxRetries; i++ {
+    if v := getScene(e, s, l); v != nil {
+      return v
+    }
+    time.Sleep(time.Second * time.Duration(i+1))
+  }
+  t.Fatalf("Failed to get scene after %d retries: %v", maxRetries, lastErr)
+  return nil
+}
server/internal/adapter/gql/resolver_mutation_asset.go (1)

41-62: Improve variable naming for consistency.

Consider the following improvements:

  1. Rename err2 to err for consistency with error handling in other methods.
  2. Use consistent variable naming between similar blocks: pid vs pidValue.

Apply this diff to improve consistency:

-	var pid *id.ProjectID
+	var projectID *id.ProjectID
 	if project := input.ProjectID; project != nil {
-		pidValue, err := gqlmodel.ToID[id.Project](*input.ProjectID)
+		pid, err := gqlmodel.ToID[id.Project](*input.ProjectID)
 		if err != nil {
 			return nil, err
 		}
-		pid = &pidValue
+		projectID = &pid
 	}

-	a, p, err2 := usecases(ctx).Asset.Update(ctx, aid, pid, getOperator(ctx))
-	if err2 != nil {
+	a, p, err := usecases(ctx).Asset.Update(ctx, aid, projectID, getOperator(ctx))
+	if err != nil {
 		return nil, err2
 	}
server/e2e/gql_asset_test.go (1)

124-179: Add error case tests for project association.

The test suite should include error cases such as:

  • Attempting to associate an asset with a non-existent project
  • Attempting to associate an asset with a project from a different workspace
  • Attempting to update an asset without proper permissions

Example test cases to add:

+    // Test error cases
+    nonExistentPid := "non-existent-project-id"
+    res = updateAsset(e, assetId0, &nonExistentPid)
+    res.Path("$.errors").Array().NotEmpty()
+
+    // Create project in different workspace
+    differentTeamId := createWorkspace(e, "different-team")
+    differentPid := createProject(e, "different-project", differentTeamId)
+    res = updateAsset(e, assetId0, &differentPid)
+    res.Path("$.errors").Array().NotEmpty()
server/internal/usecase/interactor/scene.go (2)

711-714: Consider returning the error instead of just logging it.

While the error is logged, silently continuing after a failed asset addition could lead to incomplete exports. Consider either:

  1. Returning the error to let the caller decide how to handle it
  2. Adding a comment explaining why it's safe to continue despite the error
-    u, _ := url.Parse(urlStr)
-    if err := AddZipAsset(ctx, i.file, zipWriter, u.Path); err != nil {
-        log.Infofc(ctx, "Fail nLayer addZipAsset :", err.Error())
-    }
+    u, err := url.Parse(urlStr)
+    if err != nil {
+        return nil, nil, fmt.Errorf("invalid URL in nLayer: %w", err)
+    }
+    if err := AddZipAsset(ctx, i.file, zipWriter, u.Path); err != nil {
+        return nil, nil, fmt.Errorf("failed to add nLayer asset: %w", err)
+    }

740-742: Consider returning the error instead of just logging it.

Similar to the previous comment, consider returning the error instead of just logging it to ensure proper error handling.

-    if u, ok := field.Value().Value().(*url.URL); ok {
-        if err := AddZipAsset(ctx, i.file, zipWriter, u.Path); err != nil {
-            log.Infofc(ctx, "Fail widget addZipAsset :", err.Error())
-        }
+    if u, ok := field.Value().Value().(*url.URL); ok {
+        if err := AddZipAsset(ctx, i.file, zipWriter, u.Path); err != nil {
+            return nil, nil, fmt.Errorf("failed to add widget asset: %w", err)
+        }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c10081 and ddf2e3c.

⛔ Files ignored due to path filters (4)
  • server/e2e/test.zip is excluded by !**/*.zip
  • web/src/services/gql/__gen__/fragmentMatcher.json is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/gql.ts is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/graphql.ts is excluded by !**/__gen__/**
📒 Files selected for processing (107)
  • server/Makefile (3 hunks)
  • server/e2e/common.go (7 hunks)
  • server/e2e/dataset_export_test.go (0 hunks)
  • server/e2e/gql_asset_test.go (6 hunks)
  • server/e2e/gql_custom_property_test.go (1 hunks)
  • server/e2e/gql_layer_test.go (0 hunks)
  • server/e2e/gql_me_test.go (1 hunks)
  • server/e2e/gql_nlslayer_test.go (0 hunks)
  • server/e2e/gql_project_export_import_test.go (3 hunks)
  • server/e2e/gql_project_export_test.go (0 hunks)
  • server/e2e/gql_project_test.go (6 hunks)
  • server/e2e/gql_scene_test.go (4 hunks)
  • server/e2e/gql_storytelling_test.go (6 hunks)
  • server/e2e/gql_user_test.go (2 hunks)
  • server/e2e/gql_validate_geojson_test.go (1 hunks)
  • server/e2e/mock_test.go (1 hunks)
  • server/e2e/ping_test.go (1 hunks)
  • server/e2e/seeder.go (5 hunks)
  • server/go.mod (1 hunks)
  • server/gql/asset.graphql (5 hunks)
  • server/gql/cluster.graphql (0 hunks)
  • server/gql/dataset.graphql (0 hunks)
  • server/gql/layer.graphql (0 hunks)
  • server/gql/plugin.graphql (2 hunks)
  • server/gql/property.graphql (0 hunks)
  • server/gql/scene.graphql (1 hunks)
  • server/gql/storytelling.graphql (3 hunks)
  • server/gql/tag.graphql (0 hunks)
  • server/gql/workspace.graphql (2 hunks)
  • server/gqlgen.yml (0 hunks)
  • server/internal/adapter/gql/context.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/dataloader.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_layer.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go (5 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project.go (2 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_scene.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_tag.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (9 hunks)
  • server/internal/adapter/gql/loader.go (0 hunks)
  • server/internal/adapter/gql/loader_asset.go (2 hunks)
  • server/internal/adapter/gql/loader_dataset.go (0 hunks)
  • server/internal/adapter/gql/loader_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset_schema.go (0 hunks)
  • server/internal/adapter/gql/resolver_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_asset.go (3 hunks)
  • server/internal/adapter/gql/resolver_mutation_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (6 hunks)
  • server/internal/adapter/gql/resolver_mutation_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_storytelling.go (1 hunks)
  • server/internal/adapter/gql/resolver_mutation_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_property.go (0 hunks)
  • server/internal/adapter/gql/resolver_property_test.go (0 hunks)
  • server/internal/adapter/gql/resolver_query.go (1 hunks)
  • server/internal/adapter/gql/resolver_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_team.go (1 hunks)
  • server/internal/app/app.go (2 hunks)
  • server/internal/app/auth_client.go (1 hunks)
  • server/internal/infrastructure/fs/file.go (2 hunks)
  • server/internal/infrastructure/fs/file_test.go (3 hunks)
  • server/internal/infrastructure/memory/asset.go (1 hunks)
  • server/internal/infrastructure/mongo/asset.go (2 hunks)
  • server/internal/infrastructure/mongo/mongodoc/asset.go (3 hunks)
  • server/internal/infrastructure/mongo/mongodoc/scene.go (0 hunks)
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go (4 hunks)
  • server/internal/usecase/interactor/asset.go (6 hunks)
  • server/internal/usecase/interactor/layer.go (0 hunks)
  • server/internal/usecase/interactor/nlslayer.go (3 hunks)
  • server/internal/usecase/interactor/project.go (5 hunks)
  • server/internal/usecase/interactor/project_test.go (0 hunks)
  • server/internal/usecase/interactor/scene.go (5 hunks)
  • server/internal/usecase/interactor/scene_test.go (0 hunks)
  • server/internal/usecase/interactor/storytelling.go (1 hunks)
  • server/internal/usecase/interfaces/asset.go (2 hunks)
  • server/internal/usecase/interfaces/project.go (2 hunks)
  • server/internal/usecase/interfaces/story.go (1 hunks)
  • server/internal/usecase/repo/asset.go (1 hunks)
  • server/pkg/asset/asset.go (2 hunks)
  • server/pkg/asset/builder.go (1 hunks)
  • server/pkg/asset/id.go (1 hunks)
  • server/pkg/layer/encoding/exporter.go (1 hunks)
  • server/pkg/layer/layerops/processor_test.go (0 hunks)
  • server/pkg/nlslayer/feature.go (0 hunks)
  • server/pkg/nlslayer/feature_test.go (1 hunks)
  • server/pkg/scene/builder/builder.go (1 hunks)
  • server/pkg/scene/builder/builder_test.go (0 hunks)
  • server/pkg/scene/builder/encoder.go (1 hunks)
  • server/pkg/scene/builder/encoder_test.go (0 hunks)
  • server/pkg/scene/builder/scene.go (1 hunks)
  • server/pkg/scene/builder/story.go (1 hunks)
  • server/pkg/scene/builder_test.go (0 hunks)
  • server/pkg/storytelling/story.go (4 hunks)
  • server/pkg/storytelling/story_bulider.go (1 hunks)
  • web/src/services/api/propertyApi/utils.ts (1 hunks)
⛔ Files not processed due to max files limit (6)
  • web/src/services/api/sceneApi.ts
  • web/src/services/gql/fragments/dataset.ts
  • web/src/services/gql/fragments/index.ts
  • web/src/services/gql/fragments/layer.ts
  • web/src/services/gql/fragments/property.ts
  • web/src/services/gql/queries/scene.ts
💤 Files with no reviewable changes (47)
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go
  • server/pkg/scene/builder_test.go
  • server/e2e/gql_nlslayer_test.go
  • server/internal/adapter/gql/resolver_property_test.go
  • server/pkg/scene/builder/builder_test.go
  • server/internal/usecase/interactor/project_test.go
  • server/internal/adapter/gql/gqlmodel/convert.go
  • server/internal/infrastructure/mongo/mongodoc/scene.go
  • server/pkg/nlslayer/feature.go
  • server/gql/property.graphql
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go
  • server/internal/usecase/interactor/scene_test.go
  • server/internal/adapter/gql/context.go
  • server/internal/adapter/gql/gqlmodel/convert_tag.go
  • server/internal/usecase/interactor/layer.go
  • server/internal/adapter/gql/resolver_mutation_scene.go
  • server/e2e/dataset_export_test.go
  • server/internal/adapter/gql/gqlmodel/convert_scene.go
  • server/internal/adapter/gql/loader.go
  • server/gqlgen.yml
  • server/e2e/gql_project_export_test.go
  • server/internal/adapter/gql/gqldataloader/dataloader.go
  • server/internal/adapter/gql/gqlmodel/models.go
  • server/internal/adapter/gql/gqlmodel/convert_layer.go
  • server/pkg/scene/builder/encoder_test.go
  • server/pkg/layer/layerops/processor_test.go
  • server/internal/adapter/gql/resolver_mutation_dataset.go
  • server/internal/adapter/gql/resolver_mutation_tag.go
  • server/internal/adapter/gql/resolver_scene.go
  • server/internal/adapter/gql/resolver_dataset.go
  • server/gql/dataset.graphql
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go
  • server/internal/adapter/gql/resolver_dataset_schema.go
  • server/internal/adapter/gql/resolver_tag.go
  • server/gql/cluster.graphql
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go
  • server/internal/adapter/gql/resolver_property.go
  • server/e2e/gql_layer_test.go
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go
  • server/internal/adapter/gql/loader_tag.go
  • server/internal/adapter/gql/loader_dataset.go
  • server/internal/adapter/gql/resolver_layer.go
  • server/gql/layer.graphql
  • server/internal/adapter/gql/resolver_mutation_layer.go
  • server/gql/tag.graphql
🚧 Files skipped from review as they are similar to previous changes (37)
  • server/e2e/mock_test.go
  • server/pkg/scene/builder/story.go
  • server/gql/plugin.graphql
  • server/internal/adapter/gql/gqlmodel/convert_asset.go
  • server/pkg/nlslayer/feature_test.go
  • server/internal/infrastructure/fs/file_test.go
  • server/Makefile
  • server/pkg/asset/builder.go
  • server/pkg/scene/builder/encoder.go
  • server/internal/infrastructure/fs/file.go
  • server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go
  • server/e2e/gql_validate_geojson_test.go
  • server/pkg/layer/encoding/exporter.go
  • server/internal/usecase/interfaces/story.go
  • server/e2e/gql_scene_test.go
  • server/pkg/asset/asset.go
  • server/pkg/storytelling/story_bulider.go
  • server/internal/usecase/interactor/storytelling.go
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go
  • server/gql/scene.graphql
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go
  • server/go.mod
  • server/e2e/gql_user_test.go
  • server/internal/app/auth_client.go
  • server/pkg/asset/id.go
  • server/internal/usecase/interfaces/project.go
  • server/internal/usecase/repo/asset.go
  • server/internal/adapter/gql/resolver_mutation_storytelling.go
  • server/e2e/gql_project_test.go
  • server/internal/app/app.go
  • server/gql/storytelling.graphql
  • server/internal/infrastructure/mongo/asset.go
  • server/gql/asset.graphql
  • server/internal/adapter/gql/resolver_team.go
  • server/internal/infrastructure/mongo/mongodoc/asset.go
  • server/internal/usecase/interfaces/asset.go
  • server/gql/workspace.graphql
🧰 Additional context used
🧠 Learnings (2)
server/e2e/gql_project_export_import_test.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:48-80
Timestamp: 2024-11-12T15:21:04.151Z
Learning: In `server/e2e/gql_import_export_test.go`, test functions like `TestCallImportProject` may have disabled assertions due to problems with GraphQL.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:12-44
Timestamp: 2024-11-12T15:21:04.150Z
Learning: The response body assertions in `TestCallExportProject` are disabled due to existing problems with GraphQL.
server/internal/adapter/gql/resolver_mutation_project.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:0-0
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project export functionality, the ZIP file is deleted after saving to storage.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:215-217
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project import functionality, the file names are unique and can be safely replaced uniformly using `bytes.Replace`.
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Redirect rules - reearth-web
  • GitHub Check: Header rules - reearth-web
  • GitHub Check: Pages changed - reearth-web
🔇 Additional comments (34)
server/pkg/scene/builder/scene.go (3)

11-25: LGTM! The GA fields are properly integrated.

The new Google Analytics fields (EnableGA and TrackingID) are correctly added to the sceneJSON struct with appropriate types and json tags.


27-40: LGTM! The method changes are well-structured.

The sceneJSON method has been properly updated to:

  • Accept the new GA parameters
  • Remove the layers parameter (as mentioned in the AI summary)
  • Correctly initialize the scene JSON with GA settings

42-167: LGTM! Helper methods are well-maintained.

The helper methods maintain good separation of concerns and consistent error handling patterns.

server/pkg/scene/builder/builder.go (2)

26-38: LGTM! Removed unused field.

The removal of the unused encoder field from the Builder struct improves code cleanliness.


181-181: LGTM! GA parameters correctly propagated.

The Google Analytics parameters are properly passed through to the sceneJSON method.

server/e2e/gql_storytelling_test.go (3)

15-55: Well-structured test implementation!

The test comprehensively covers story CRUD operations and properly validates the new Google Analytics parameters (enableGa and trackingId).


564-565: GraphQL queries properly updated for GA support!

The changes correctly integrate the new Google Analytics fields (enableGa and trackingId) into both the UpdateStory mutation and GetScene query.

Also applies to: 501-514


232-413: Comprehensive publishing test implementation!

The test thoroughly validates the story publishing functionality, including proper handling of Google Analytics fields in the published output.

server/internal/adapter/gql/gqlmodel/convert_project.go (1)

5-5: LGTM!

The addition of the net/url import is necessary for the new ProjectExport type and follows proper import ordering.

web/src/services/api/propertyApi/utils.ts (2)

247-248: Inconsistency with PR objectives.

The changes in this file appear unrelated to the PR's stated objective of adding Google Analytics support to the Story component. Please clarify if these changes are intentional or if they were accidentally included in this PR.

Likely an incorrect or invalid review comment.


247-248:

✅ Verification successful

Verify the removal of actualValue from merged property fields.

The removal of actualValue from the Pick type suggests a breaking change in how merged property fields are handled. This change appears to be part of a larger refactoring effort.

Run the following script to verify all usages of merged property fields and ensure they don't rely on actualValue:


🏁 Script executed:

#!/bin/bash
# Description: Search for usages of merged property fields to ensure they don't rely on actualValue

# Search for property field usages
rg -A 5 'MergedPropertyField'

# Search for actualValue references
rg 'actualValue'

Length of output: 67037


Removal of actualValue Verified and Confirmed

The verification confirms that the actualValue property is no longer in use. The grep output shows that the only traces of actualValue are in the commented code (with no downstream references relying on it), and the generated GraphQL types have been updated accordingly. Note, however, that the refactoring—including the removal of actualValue—appears unrelated to the PR’s stated goal of adding Google Analytics support to the Story component. Please confirm that the additional refactoring changes were intentional and align with your overall objectives.

  • File Impact:
    • web/src/services/api/propertyApi/utils.ts (changes to the merged property field type)
server/e2e/gql_project_export_import_test.go (3)

21-63: LGTM! Well-structured test with clear steps.

The test function is well-organized with clear comments indicating each step of the process. The use of fullSeeder provides a more comprehensive test environment.


71-101: Consider adding test coverage for edge cases in compareValue.

The compareValue function handles basic comparison well, but consider adding test coverage for:

  • Empty arrays
  • Arrays with different lengths
  • Objects with different structures

Would you like me to generate test cases for these scenarios?


141-170:

❓ Verification inconclusive

Consider adding validation for imported project data.

The importProject function should validate the structure of the imported data before returning.


🏁 Script executed:

#!/bin/bash
# Description: Check if the imported project data matches the expected schema

# Search for project data validation in the codebase
rg -A 5 'type ProjectData struct'

Length of output: 35


Action Required: Enhance Validation for Imported Project Data

After examining the current implementation in server/e2e/gql_project_export_import_test.go (lines 141–170), it appears that the test only confirms that projectData is not null. Our initial search for a defined ProjectData struct produced no results, which suggests that robust schema validation for the imported project data might be missing.

  • Current Behavior:

    • The function only checks for non-nil data via projectData.NotNull().
  • Recommendation:

    • Consider adding validation to explicitly verify that the imported project data includes all expected fields and conforms to a defined schema.
    • If a ProjectData type or similar schema definition is intended but missing, adding it could improve the test's effectiveness and overall data integrity.

Please review these suggestions and implement the necessary validation logic as appropriate.

server/internal/adapter/gql/resolver_query.go (2)

1-225: Verify alignment with PR objectives.

The changes in this file focus on asset handling and are unrelated to the PR's stated objective of adding Google Analytics parameters to the Story component. Please verify if:

  1. The PR description is accurate
  2. All relevant files have been included in the review
  3. These changes should be in a separate PR

15-16:

✅ Verification successful

LGTM! The Assets query now supports project-based filtering.

The addition of the projectId parameter allows for more granular asset filtering by project, which is a good improvement to the API.

Let's verify that the loader's FindByWorkspace method signature matches this change:


🏁 Script executed:

#!/bin/bash
# Description: Verify the loader's FindByWorkspace method signature matches the resolver.

# Search for the FindByWorkspace method definition in the loader
ast-grep --pattern 'func $_(ctx context.Context, teamID gqlmodel.ID, projectId *gqlmodel.ID, $$$) ($_, error)' 

Length of output: 112


Let's run an additional verification to ensure that the loader's FindByWorkspace function signature includes the new projectId parameter. Please run the following script to search for the function definition using rg:

If the output confirms that the function signature includes projectId *gqlmodel.ID (along with the other parameters), then the change in the resolver is fully reflected in the loader. Otherwise, please update the loader accordingly.


Please verify that the loader’s FindByWorkspace method signature is updated to match the resolver’s call, including the projectId parameter.


🏁 Script executed:

[run_scripts]
#!/bin/bash
# Searching for the FindByWorkspace function definition using rg to verify parameter list.
rg -n "func .*FindByWorkspace"

Length of output: 2443


LGTM: The Assets query now includes project-based filtering.
The new projectId parameter in the resolver is correctly propagated to the loader. The grep output confirms that the FindByWorkspace method in server/internal/adapter/gql/loader_asset.go accepts a *gqlmodel.ID as its third parameter, which aligns with the updated resolver implementation.

  • Location: server/internal/adapter/gql/loader_asset.go, line 39
server/e2e/gql_me_test.go (1)

8-8: LGTM!

The server initialization has been simplified while maintaining the test functionality.

server/e2e/ping_test.go (1)

9-9: LGTM!

The server initialization has been simplified while maintaining the test functionality.

server/internal/adapter/gql/resolver_mutation_asset.go (1)

19-31: LGTM!

The project ID handling is well-implemented with proper error checking and follows consistent patterns.

server/internal/adapter/gql/loader_asset.go (2)

45-54: LGTM!

The project ID handling is well-implemented with proper error checking and follows consistent patterns.


60-66: LGTM!

The edge creation has been optimized by:

  1. Preallocating the slice to avoid resizing.
  2. Simplifying the loop structure.
server/internal/infrastructure/memory/asset.go (1)

49-59: LGTM! Project filtering logic is well implemented.

The new project filtering logic is correctly implemented with proper null checks and maintains backward compatibility when pid is nil.

server/internal/usecase/interactor/asset.go (1)

39-51: LGTM! Method signature update is consistent.

The method signature update to include project ID is consistent with the repository layer changes.

server/e2e/common.go (1)

283-297: LGTM! ValueDump function handles different types appropriately.

The function now properly handles different types of values and includes a default case for unsupported types.

server/internal/adapter/gql/resolver_mutation_project.go (1)

82-84: LGTM! GA parameters successfully integrated.

The addition of enableGa and trackingId fields to the project update parameters aligns with the PR objectives to support Google Analytics integration.

server/internal/usecase/interactor/project.go (1)

258-264: LGTM! GA parameters successfully integrated.

The addition of UpdateEnableGA and UpdateTrackingID methods properly handles the new Google Analytics parameters, aligning with the PR objectives.

server/e2e/gql_custom_property_test.go (1)

347-762: LGTM! Query structure significantly improved.

The GraphQL query has been refactored into a well-structured format with proper fragments, enhancing readability and maintainability. The use of fragments like PropertyFragment, PluginFragment, etc., promotes code reuse.

server/internal/usecase/interactor/scene.go (2)

22-22: LGTM!

The import of the storytelling package is necessary for the GA integration changes.


680-683: LGTM! Improved nil handling.

The change adds proper nil and length checks before accessing the story list, preventing potential nil pointer dereferences.

server/internal/usecase/interactor/nlslayer.go (2)

859-861: LGTM!

The change correctly uses the new NewFeature API with explicit ID creation.


1109-1134: LGTM! Improved code readability.

The changes enhance the feature collection creation with:

  • Better variable names
  • Clearer structure
  • Consistent use of the new NewFeature API
server/pkg/storytelling/story.go (2)

41-42: LGTM!

The new fields enableGa and trackingID are well-named and use appropriate types for Google Analytics integration.


57-62: LGTM!

Good addition of the nil check to prevent potential nil pointer dereferences.

server/internal/adapter/gql/gqlmodel/models_gen.go (1)

1221-1222: LGTM!

The GraphQL model changes are consistent with the Story struct changes, correctly exposing the Google Analytics fields in both the Story type and UpdateStoryInput.

Also applies to: 1459-1460

@hexaforce hexaforce force-pushed the feat/storytelling-ga-support branch 5 times, most recently from 54e2849 to b36253e Compare February 20, 2025 04:54
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (7)
server/internal/infrastructure/mongo/asset.go (1)

96-100: ⚠️ Potential issue

Handle type assertion failure case.

The type assertion to bson.M could silently fail if the assertion is unsuccessful. Consider handling the failure case explicitly.

Apply this diff to handle the failure case:

-	if andFilter, ok := mongox.And(filter, "url", bson.M{
-		"$regex": primitive.Regex{Pattern: bucketPattern, Options: "i"},
-	}).(bson.M); ok {
-		filter = andFilter
-	}
+	if andResult := mongox.And(filter, "url", bson.M{
+		"$regex": primitive.Regex{Pattern: bucketPattern, Options: "i"},
+	}); andResult != nil {
+		if andFilter, ok := andResult.(bson.M); ok {
+			filter = andFilter
+		}
+	}
server/internal/usecase/interactor/asset.go (1)

108-127: ⚠️ Potential issue

Add validation for project access and existence.

The function should verify:

  1. The target project exists
  2. The operator has write permissions for the target project's workspace

Apply this diff to improve the implementation:

 func (i *Asset) Update(ctx context.Context, aid id.AssetID, pid *id.ProjectID, operator *usecase.Operator) (id.AssetID, *id.ProjectID, error) {
 	return Run2(
 		ctx, operator, i.repos,
 		Usecase().Transaction(),
 		func(ctx context.Context) (id.AssetID, *id.ProjectID, error) {
 			asset, err := i.repos.Asset.FindByID(ctx, aid)
 			if err != nil {
 				return aid, pid, err
 			}
 
 			if ok := operator.IsWritableWorkspace(asset.Workspace()); !ok {
 				return aid, pid, interfaces.ErrOperationDenied
 			}
+
+			if pid != nil {
+				project, err := i.repos.Project.FindByID(ctx, *pid)
+				if err != nil {
+					return aid, pid, err
+				}
+				if project == nil {
+					return aid, pid, interfaces.ErrNotFound
+				}
+				if ok := operator.IsWritableWorkspace(project.Workspace()); !ok {
+					return aid, pid, interfaces.ErrOperationDenied
+				}
+			}
 
 			asset.SetProject(pid)
 
 			return aid, pid, i.repos.Asset.Save(ctx, asset)
 		},
 	)
 }
server/e2e/common.go (1)

266-271: ⚠️ Potential issue

Add type assertion error handling in JSONEqRegexpValue.

The type assertion discards the error case, which could hide issues.

Add proper error handling:

 func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
-    if actualData, ok := actual.Raw().(map[string]interface{}); ok {
-        return JSONEqRegexpInterface(t, actualData, expected)
-    }
-    return false
+    actualData, ok := actual.Raw().(map[string]interface{})
+    if !ok {
+        t.Errorf("expected map[string]interface{}, got %T", actual.Raw())
+        return false
+    }
+    return JSONEqRegexpInterface(t, actualData, expected)
 }
server/e2e/gql_asset_test.go (1)

224-240: ⚠️ Potential issue

Add error handling for updateAsset response.

The function should check for errors in the GraphQL response.

Add error handling:

 func updateAsset(e *httpexpect.Expect, assetId string, projectId *string) *httpexpect.Value {
     requestBody := GraphQLRequest{
         OperationName: "UpdateAsset",
         Query: `mutation UpdateAsset($assetId: ID!, $projectId: ID) {
             updateAsset(input: { assetId: $assetId projectId: $projectId }) {
                 assetId
                 projectId
+                errors {
+                    message
+                    path
+                }
                 __typename
             }
         }`,
         Variables: map[string]any{
             "assetId":   assetId,
             "projectId": projectId,
         },
     }
-    return Request(e, uID.String(), requestBody)
+    res := Request(e, uID.String(), requestBody)
+    if errors := res.Path("$.errors").Array(); len(errors.Raw()) > 0 {
+        return nil
+    }
+    return res
 }
server/internal/adapter/gql/resolver_mutation_project.go (2)

212-214: ⚠️ Potential issue

Handle potential error during workspace ID conversion.

The workspace ID conversion discards the error, which could lead to runtime issues.

Add error handling:

-    workspace, _ := accountdomain.WorkspaceIDFrom(string(input.TeamID))
+    workspace, err := accountdomain.WorkspaceIDFrom(string(input.TeamID))
+    if err != nil {
+        return nil, fmt.Errorf("invalid team ID: %w", err)
+    }

224-224: ⚠️ Potential issue

Handle potential error from unmarshalProject.

The unmarshalProject error is discarded, which could lead to nil dereference.

Add error handling:

-    projectData, _ := unmarshalProject(tempData)
+    projectData, err := unmarshalProject(tempData)
+    if err != nil {
+        return nil, fmt.Errorf("failed to unmarshal project data: %w", err)
+    }
server/e2e/seeder.go (1)

203-222: ⚠️ Potential issue

Fix incorrect error check conditions.

Multiple error checks use if err != err which is logically incorrect and will never evaluate to true. This causes the error handling code to be unreachable.

Fix all instances by replacing with if err != nil:

-    if err = r.Scene.Save(ctx, s); err != err {
+    if err = r.Scene.Save(ctx, s); err != nil {
         return err
     }
-    if err = r.Property.Save(ctx, prop); err != err {
+    if err = r.Property.Save(ctx, prop); err != nil {
         return err
     }
-    if err = r.Layer.Save(ctx, rootLayer); err != err {
+    if err = r.Layer.Save(ctx, rootLayer); err != nil {
         return err
     }
-    if err = addWidget(ctx, s, r); err != err {
+    if err = addWidget(ctx, s, r); err != nil {
         return err
     }
-    if err = addStory(ctx, r); err != err {
+    if err = addStory(ctx, r); err != nil {
         return err
     }
-    if err = addLayerSimple(ctx, r); err != err {
+    if err = addLayerSimple(ctx, r); err != nil {
         return err
     }
🧹 Nitpick comments (16)
server/internal/usecase/interactor/storytelling_test.go (1)

25-196: Add test cases for enabled Google Analytics scenarios.

The current test only verifies the default values (enableGa: false and empty trackingId). Consider adding test cases to verify:

  1. Story import with GA enabled (enableGa: true)
  2. Story import with a valid tracking ID
  3. Story import with both GA enabled and tracking ID set

This will ensure the GA functionality is thoroughly tested.

Here's a suggested test case to add:

func TestImportStoryWithGA(t *testing.T) {
    // ... (similar setup as TestImportStory)
    
    var sceneData map[string]interface{}
    err := json.Unmarshal([]byte(fmt.Sprintf(`{
        "schemaVersion": 1,
        "id": "%s",
        "story": {
            "id": "01j7g9ddvkarms2gmc59ysw66r",
            "property": {},
            "pages": [],
            "enableGa": true,
            "trackingId": "UA-12345678-9"
        }
    }`, scene.ID())), &sceneData)
    assert.NoError(t, err)

    result, err := ifs.ImportStory(ctx, scene.ID(), sceneData, map[string]idx.ID[id.NLSLayer]{})
    assert.NoError(t, err)
    assert.NotNil(t, result)
    assert.True(t, result.EnableGa())
    assert.Equal(t, "UA-12345678-9", result.TrackingId())
}
server/internal/usecase/interactor/property_test.go (1)

22-23: LGTM! Scene initialization simplified.

The removal of root layer specification simplifies scene creation without affecting the property tests' functionality.

Consider adding edge cases to improve test coverage.

The current tests focus on happy paths. Consider adding test cases for:

  • Invalid property values
  • Non-existent properties
  • Boundary conditions for list operations

Also applies to: 74-75, 119-120

server/internal/usecase/interactor/nlslayer_test.go (1)

34-35: LGTM! Scene initialization simplified.

The removal of root layer specification simplifies scene creation without affecting the NLS layer tests' functionality.

Consider improving URL handling in tests.

The tests use hardcoded URLs like "https://example.com" and "http://localhost:8080". Consider:

  • Using environment variables for URLs
  • Adding test cases for invalid URLs
  • Mocking URL responses for more robust testing

Also applies to: 85-86, 142-143, 221-222, 289-290

server/internal/adapter/gql/gqlmodel/models_gen.go (2)

138-138: Ensure references to optionally associated projects are validated.
Adding ProjectID here is potentially useful for linking assets to projects, but please verify that all references are updated accordingly in the resolvers or services. If the asset is meant to always have a project, consider making this field mandatory instead of optional.


581-588: Validate dynamic typing for 'Value'.
The Value any approach can hamper type safety. If fully dynamic values are intentional, ensure runtime checks or validations prevent inadvertent type errors. Also confirm that SchemaID and FieldID correctly reference valid schema entities.

server/e2e/gql_scene_test.go (1)

77-100: Enhance the createProjectWithExternalImage function.

While the function is well-structured, consider these improvements:

  1. Add error handling for failed requests.
  2. Parameterize hard-coded values like description and imageUrl.

Here's a suggested improvement:

 func createProjectWithExternalImage(e *httpexpect.Expect, name string) string {
+    // Add more parameters for flexibility
+    func createProjectWithExternalImage(e *httpexpect.Expect, name, description, imageUrl string) (string, error) {
     requestBody := GraphQLRequest{
         OperationName: "CreateProject",
         Query: `mutation CreateProject($teamId: ID!, $visualizer: Visualizer!, $name: String!, $description: String!, $imageUrl: URL, $coreSupport: Boolean) {
             createProject( input: {teamId: $teamId, visualizer: $visualizer, name: $name, description: $description, imageUrl: $imageUrl, coreSupport: $coreSupport} ) { 
                 project { 
                     id
+                    success
+                    error
                     __typename 
                 } 
                 __typename 
             }
         }`,
         Variables: map[string]any{
             "name":        name,
-            "description": "abc",
-            "imageUrl":    "https://test.com/project.jpg",
+            "description": description,
+            "imageUrl":    imageUrl,
             "teamId":      wID.String(),
             "visualizer":  "CESIUM",
             "coreSupport": true,
         },
     }
     res := Request(e, uID.String(), requestBody)
-    return res.Path("$.data.createProject.project.id").Raw().(string)
+    
+    // Add error handling
+    if err := res.Path("$.data.createProject.project.error").Raw(); err != nil {
+        return "", fmt.Errorf("failed to create project: %v", err)
+    }
+    
+    return res.Path("$.data.createProject.project.id").Raw().(string), nil
 }
server/internal/adapter/gql/resolver_mutation_scene.go (1)

20-20: Document the purpose of the boolean parameter.

The purpose of the false parameter in usecases(ctx).Scene.Create is not clear. Please add a comment explaining its significance.

server/e2e/gql_validate_geojson_test.go (2)

201-236: Add timeout handling for external URL tests.

The test cases for external URLs could potentially hang if the remote server is slow to respond or unreachable.

Add a timeout context to the test:

 func TestValidateGeoJsonExternal(t *testing.T) {
+    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+    defer cancel()
     tests := []struct {
         name     string
         url      string
         hasError bool
     }{

238-313: Enhance test coverage for GeoJSON validation.

The test cases only cover Point geometry. Consider adding test cases for other geometry types like LineString, Polygon, etc.

Add more test cases:

 tests := []struct {
     name     string
     geometry map[string]any
     hasError bool
 }{
     {
         name: "Valid Point",
         geometry: map[string]any{
             "type":        "Point",
             "coordinates": []float64{125.6, 10.1},
         },
         hasError: false,
     },
+    {
+        name: "Valid LineString",
+        geometry: map[string]any{
+            "type":        "LineString",
+            "coordinates": [][]float64{{125.6, 10.1}, {125.7, 10.2}},
+        },
+        hasError: false,
+    },
+    {
+        name: "Valid Polygon",
+        geometry: map[string]any{
+            "type":        "Polygon",
+            "coordinates": [][][]float64{{{125.6, 10.1}, {125.7, 10.2}, {125.8, 10.3}, {125.6, 10.1}}},
+        },
+        hasError: false,
+    },
     // ... existing test cases
server/e2e/common.go (1)

35-43: Consider making disabledAuthConfig immutable.

The configuration object is shared across tests and could potentially be modified.

Make the config immutable:

-var (
+var fr *gateway.File
+
+var disabledAuthConfig = func() *config.Config {
+    return &config.Config{
+        Origins: []string{"https://example.com"},
+        AuthSrv: config.AuthSrvConfig{
+            Disabled: true,
+        },
+    }
+}()
server/internal/adapter/gql/resolver_mutation_project.go (1)

338-386: Add input validation in helper functions.

The helper functions don't validate input data before processing.

Add input validation:

 func unmarshalProject(data []byte) (map[string]interface{}, error) {
+    if len(data) == 0 {
+        return nil, errors.New("empty data")
+    }
     var jsonData map[string]interface{}
     if err := json.Unmarshal(data, &jsonData); err != nil {
         return nil, err
     }
 
     projectData, _ := jsonData["project"].(map[string]interface{})
+    if projectData == nil {
+        return nil, errors.New("project data not found")
+    }
     return projectData, nil
 }
server/e2e/gql_project_export_import_test.go (2)

59-62: Add error handling for file cleanup.

The deferred cleanup should handle errors more gracefully.

 defer func() {
-    err := os.Remove(fileName)
-    assert.Nil(t, err)
+    if err := os.Remove(fileName); err != nil {
+        t.Logf("Warning: Failed to remove temporary file %s: %v", fileName, err)
+    }
 }()

72-102: Add validation for empty or nil values in compareValue.

The function should validate input parameters and handle edge cases.

 func compareValue(t *testing.T, key string, e, a *httpexpect.Value) {
+    if e == nil || a == nil {
+        t.Fatalf("compareValue: expected and actual values must not be nil")
+        return
+    }
+    if key == "" {
+        t.Fatalf("compareValue: key must not be empty")
+        return
+    }
+
     expected := convertLine(t, key, e)
     actual := convertLine(t, key, a)
server/internal/usecase/interactor/project.go (1)

513-533: Improve error messages in ExportProjectData.

The error messages could be more descriptive and include context.

     prj, err := i.projectRepo.FindByID(ctx, projectID)
     if err != nil {
-        return nil, errors.New("project " + err.Error())
+        return nil, fmt.Errorf("failed to find project %s: %w", projectID, err)
     }
     if prj.IsDeleted() {
-        fmt.Printf("Error Deleted project: %v\n", prj.ID())
-        return nil, errors.New("This project is deleted")
+        return nil, fmt.Errorf("cannot export deleted project: %s", prj.ID())
     }
server/e2e/gql_custom_property_test.go (1)

347-762: Consider moving the GraphQL query to a separate file.

The query is well-structured but quite long. Moving it to a separate file would improve maintainability and reusability.

  1. Create a new file server/e2e/queries/get_scene.graphql with the query content.
  2. Update the code to read the query from the file:
 func getNewLayersOfScene(e *httpexpect.Expect, sId string) *httpexpect.Object {
+    query, err := os.ReadFile("queries/get_scene.graphql")
+    if err != nil {
+        panic(fmt.Errorf("failed to read GraphQL query: %w", err))
+    }
     requestBody := GraphQLRequest{
         OperationName: "GetScene",
-        Query: `query GetScene($sceneId: ID!, $lang: Lang) {
-            ...
-        }`,
+        Query: string(query),
         Variables: map[string]any{
             "sceneId": sId,
             "lang":    "en",
         },
     }
server/internal/usecase/interactor/scene.go (1)

100-159: Enhance property saving with import-specific filtering.

The new isImportProject parameter controls whether to apply scene filtering during property saving, improving security and access control for imported projects.

Consider adding a comment explaining why imported projects require filtered property saving for better maintainability.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ddf2e3c and edc304a.

⛔ Files ignored due to path filters (4)
  • server/e2e/test.zip is excluded by !**/*.zip
  • web/src/services/gql/__gen__/fragmentMatcher.json is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/gql.ts is excluded by !**/__gen__/**
  • web/src/services/gql/__gen__/graphql.ts is excluded by !**/__gen__/**
📒 Files selected for processing (107)
  • server/Makefile (3 hunks)
  • server/e2e/common.go (7 hunks)
  • server/e2e/dataset_export_test.go (0 hunks)
  • server/e2e/gql_asset_test.go (6 hunks)
  • server/e2e/gql_custom_property_test.go (1 hunks)
  • server/e2e/gql_layer_test.go (0 hunks)
  • server/e2e/gql_me_test.go (1 hunks)
  • server/e2e/gql_nlslayer_test.go (0 hunks)
  • server/e2e/gql_project_export_import_test.go (3 hunks)
  • server/e2e/gql_project_export_test.go (0 hunks)
  • server/e2e/gql_project_test.go (6 hunks)
  • server/e2e/gql_scene_test.go (4 hunks)
  • server/e2e/gql_storytelling_test.go (6 hunks)
  • server/e2e/gql_user_test.go (2 hunks)
  • server/e2e/gql_validate_geojson_test.go (2 hunks)
  • server/e2e/mock_test.go (1 hunks)
  • server/e2e/ping_test.go (1 hunks)
  • server/e2e/seeder.go (5 hunks)
  • server/go.mod (1 hunks)
  • server/gql/asset.graphql (5 hunks)
  • server/gql/cluster.graphql (0 hunks)
  • server/gql/dataset.graphql (0 hunks)
  • server/gql/layer.graphql (0 hunks)
  • server/gql/plugin.graphql (2 hunks)
  • server/gql/property.graphql (0 hunks)
  • server/gql/scene.graphql (1 hunks)
  • server/gql/storytelling.graphql (3 hunks)
  • server/gql/tag.graphql (0 hunks)
  • server/gql/workspace.graphql (2 hunks)
  • server/gqlgen.yml (0 hunks)
  • server/internal/adapter/gql/context.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/dataloader.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_layer.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go (5 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project.go (2 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_scene.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_tag.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models.go (0 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (9 hunks)
  • server/internal/adapter/gql/loader.go (0 hunks)
  • server/internal/adapter/gql/loader_asset.go (2 hunks)
  • server/internal/adapter/gql/loader_dataset.go (0 hunks)
  • server/internal/adapter/gql/loader_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_dataset_schema.go (0 hunks)
  • server/internal/adapter/gql/resolver_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_asset.go (3 hunks)
  • server/internal/adapter/gql/resolver_mutation_dataset.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_layer.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (6 hunks)
  • server/internal/adapter/gql/resolver_mutation_scene.go (1 hunks)
  • server/internal/adapter/gql/resolver_mutation_storytelling.go (1 hunks)
  • server/internal/adapter/gql/resolver_mutation_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_property.go (0 hunks)
  • server/internal/adapter/gql/resolver_property_test.go (0 hunks)
  • server/internal/adapter/gql/resolver_query.go (1 hunks)
  • server/internal/adapter/gql/resolver_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_tag.go (0 hunks)
  • server/internal/adapter/gql/resolver_team.go (1 hunks)
  • server/internal/app/app.go (2 hunks)
  • server/internal/app/auth_client.go (1 hunks)
  • server/internal/infrastructure/fs/file.go (2 hunks)
  • server/internal/infrastructure/fs/file_test.go (3 hunks)
  • server/internal/infrastructure/memory/asset.go (1 hunks)
  • server/internal/infrastructure/mongo/asset.go (2 hunks)
  • server/internal/infrastructure/mongo/mongodoc/asset.go (3 hunks)
  • server/internal/infrastructure/mongo/mongodoc/scene.go (0 hunks)
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go (4 hunks)
  • server/internal/usecase/interactor/asset.go (6 hunks)
  • server/internal/usecase/interactor/layer.go (1 hunks)
  • server/internal/usecase/interactor/layer_test.go (1 hunks)
  • server/internal/usecase/interactor/nlslayer.go (7 hunks)
  • server/internal/usecase/interactor/nlslayer_test.go (5 hunks)
  • server/internal/usecase/interactor/plugin_test.go (1 hunks)
  • server/internal/usecase/interactor/plugin_upload_test.go (5 hunks)
  • server/internal/usecase/interactor/project.go (5 hunks)
  • server/internal/usecase/interactor/project_test.go (0 hunks)
  • server/internal/usecase/interactor/property_test.go (3 hunks)
  • server/internal/usecase/interactor/scene.go (7 hunks)
  • server/internal/usecase/interactor/scene_plugin.go (0 hunks)
  • server/internal/usecase/interactor/scene_plugin_test.go (3 hunks)
  • server/internal/usecase/interactor/scene_test.go (1 hunks)
  • server/internal/usecase/interactor/storytelling.go (1 hunks)
  • server/internal/usecase/interactor/storytelling_test.go (2 hunks)
  • server/internal/usecase/interactor/style_test.go (1 hunks)
  • server/internal/usecase/interfaces/asset.go (2 hunks)
  • server/internal/usecase/interfaces/project.go (2 hunks)
  • server/internal/usecase/interfaces/scene.go (1 hunks)
  • server/internal/usecase/interfaces/story.go (1 hunks)
  • server/internal/usecase/repo/asset.go (1 hunks)
  • server/pkg/asset/asset.go (2 hunks)
  • server/pkg/asset/builder.go (1 hunks)
  • server/pkg/asset/id.go (1 hunks)
  • server/pkg/layer/encoding/exporter.go (1 hunks)
  • server/pkg/layer/initializer_test.go (0 hunks)
  • server/pkg/layer/layerops/processor_test.go (0 hunks)
  • server/pkg/nlslayer/feature.go (0 hunks)
⛔ Files not processed due to max files limit (21)
  • server/pkg/nlslayer/feature_test.go
  • server/pkg/scene/builder.go
  • server/pkg/scene/builder/builder.go
  • server/pkg/scene/builder/builder_test.go
  • server/pkg/scene/builder/encoder.go
  • server/pkg/scene/builder/encoder_test.go
  • server/pkg/scene/builder/scene.go
  • server/pkg/scene/builder/story.go
  • server/pkg/scene/builder_test.go
  • server/pkg/scene/scene.go
  • server/pkg/scene/scene_test.go
  • server/pkg/scene/sceneops/plugin_migrator_test.go
  • server/pkg/storytelling/story.go
  • server/pkg/storytelling/story_bulider.go
  • web/src/services/api/propertyApi/utils.ts
  • web/src/services/api/sceneApi.ts
  • web/src/services/gql/fragments/dataset.ts
  • web/src/services/gql/fragments/index.ts
  • web/src/services/gql/fragments/layer.ts
  • web/src/services/gql/fragments/property.ts
  • web/src/services/gql/queries/scene.ts
💤 Files with no reviewable changes (43)
  • server/internal/adapter/gql/gqlmodel/convert_plugin.go
  • server/internal/adapter/gql/resolver_property_test.go
  • server/internal/usecase/interactor/scene_plugin.go
  • server/pkg/nlslayer/feature.go
  • server/internal/adapter/gql/context.go
  • server/internal/adapter/gql/gqlmodel/models.go
  • server/pkg/layer/initializer_test.go
  • server/internal/usecase/interactor/project_test.go
  • server/e2e/gql_nlslayer_test.go
  • server/internal/adapter/gql/gqlmodel/convert.go
  • server/internal/adapter/gql/loader.go
  • server/gql/property.graphql
  • server/internal/adapter/gql/gqlmodel/convert_dataset.go
  • server/e2e/dataset_export_test.go
  • server/e2e/gql_project_export_test.go
  • server/internal/adapter/gql/gqldataloader/dataloader.go
  • server/pkg/layer/layerops/processor_test.go
  • server/e2e/gql_layer_test.go
  • server/internal/adapter/gql/resolver_layer.go
  • server/gqlgen.yml
  • server/internal/adapter/gql/gqlmodel/convert_tag.go
  • server/internal/adapter/gql/gqlmodel/convert_layer.go
  • server/internal/adapter/gql/gqlmodel/convert_scene.go
  • server/internal/adapter/gql/resolver_scene.go
  • server/internal/infrastructure/mongo/mongodoc/scene.go
  • server/internal/adapter/gql/resolver_dataset.go
  • server/gql/cluster.graphql
  • server/internal/adapter/gql/resolver_mutation_tag.go
  • server/internal/adapter/gql/resolver_mutation_dataset.go
  • server/gql/dataset.graphql
  • server/internal/adapter/gql/gqldataloader/tagitemloader_gen.go
  • server/gql/tag.graphql
  • server/internal/adapter/gql/gqldataloader/taggrouploader_gen.go
  • server/internal/adapter/gql/gqldataloader/datasetschemaloader_gen.go
  • server/internal/adapter/gql/resolver_dataset_schema.go
  • server/internal/adapter/gql/resolver_tag.go
  • server/internal/adapter/gql/resolver_property.go
  • server/internal/adapter/gql/resolver_mutation_layer.go
  • server/internal/adapter/gql/gqldataloader/tagloader_gen.go
  • server/internal/adapter/gql/loader_tag.go
  • server/internal/adapter/gql/gqldataloader/datasetloader_gen.go
  • server/gql/layer.graphql
  • server/internal/adapter/gql/loader_dataset.go
🚧 Files skipped from review as they are similar to previous changes (33)
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go
  • server/e2e/mock_test.go
  • server/pkg/asset/builder.go
  • server/internal/adapter/gql/resolver_mutation_storytelling.go
  • server/Makefile
  • server/pkg/layer/encoding/exporter.go
  • server/internal/usecase/interfaces/story.go
  • server/internal/usecase/interactor/scene_test.go
  • server/gql/plugin.graphql
  • server/go.mod
  • server/internal/infrastructure/fs/file_test.go
  • server/e2e/gql_user_test.go
  • server/internal/infrastructure/fs/file.go
  • server/internal/app/app.go
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go
  • server/pkg/asset/id.go
  • server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go
  • server/internal/adapter/gql/loader_asset.go
  • server/pkg/asset/asset.go
  • server/gql/storytelling.graphql
  • server/internal/infrastructure/mongo/mongodoc/asset.go
  • server/gql/asset.graphql
  • server/internal/adapter/gql/gqlmodel/convert_asset.go
  • server/gql/workspace.graphql
  • server/internal/infrastructure/memory/asset.go
  • server/internal/usecase/interfaces/asset.go
  • server/internal/app/auth_client.go
  • server/internal/usecase/interfaces/project.go
  • server/internal/adapter/gql/resolver_mutation_asset.go
  • server/internal/usecase/repo/asset.go
  • server/internal/adapter/gql/resolver_team.go
  • server/internal/usecase/interactor/nlslayer.go
  • server/internal/usecase/interactor/storytelling.go
🧰 Additional context used
🧠 Learnings (2)
server/internal/adapter/gql/resolver_mutation_project.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:0-0
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project export functionality, the ZIP file is deleted after saving to storage.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:215-217
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project import functionality, the file names are unique and can be safely replaced uniformly using `bytes.Replace`.
server/e2e/gql_project_export_import_test.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:48-80
Timestamp: 2024-11-12T15:21:04.151Z
Learning: In `server/e2e/gql_import_export_test.go`, test functions like `TestCallImportProject` may have disabled assertions due to problems with GraphQL.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:12-44
Timestamp: 2024-11-12T15:21:04.150Z
Learning: The response body assertions in `TestCallExportProject` are disabled due to existing problems with GraphQL.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: ci-server / ci-server-lint
🔇 Additional comments (35)
server/internal/infrastructure/mongo/asset.go (2)

67-67: LGTM! Method signature updated to support project-based filtering.

The method signature change is well-structured, making the project ID parameter optional through the pointer type.


72-80: LGTM! Clear and efficient filter logic implementation.

The filter logic elegantly handles both project-specific and team-wide asset filtering, maintaining backward compatibility while adding new functionality.

server/internal/usecase/interactor/storytelling_test.go (2)

35-35: LGTM! Scene initialization looks good.

The scene initialization correctly includes the workspace and project IDs, and the removal of RootLayer is consistent with similar changes across other test files.


164-164: LGTM! GA fields added as per requirements.

The new fields enableGa and trackingId have been added with appropriate default values.

Also applies to: 187-187

server/internal/usecase/interactor/layer_test.go (1)

19-20: LGTM! Scene initialization simplified.

The removal of root layer specification simplifies scene creation without affecting the test's functionality, as the scene object is only used for ID reference in the infobox creation test.

server/internal/usecase/interactor/style_test.go (1)

29-30: LGTM! Scene initialization simplified.

The removal of root layer specification simplifies scene creation without affecting the style import test's functionality, as the scene object is only used for ID reference in the JSON data.

server/internal/usecase/interactor/scene_plugin_test.go (1)

101-101: LGTM! Scene initialization simplified.

The removal of root layer from scene initialization aligns with the broader refactoring effort while maintaining test functionality.

server/internal/usecase/interactor/plugin_upload_test.go (1)

98-98: LGTM! Scene initialization simplified.

The removal of root layer from scene initialization aligns with the broader refactoring effort while maintaining test functionality.

server/internal/usecase/interactor/layer.go (1)

1062-1062:

❓ Verification inconclusive

Verify the impact of returning nil layers.

The ImportLayer function now returns nil for the layer list instead of the actual imported layers. This change could affect callers that expect a non-nil layer list.

Run the following script to find potential callers that might be affected:


🏁 Script executed:

#!/bin/bash
# Search for calls to ImportLayer to verify handling of returned layers
rg -A 5 'ImportLayer\(' 

Length of output: 1017


Action Required: Verify Nil Layer List Handling in ImportLayer

The updated ImportLayer function now deliberately returns nil for the layer list (first return value) instead of an actual list. Our search indicates that the function is defined in both the interactor and its interface without any obvious caller-side checks against a nil layer list. This change may lead to unintended nil dereferences or logic errors in parts of the code that assume a valid, non-nil layer list.

  • Ensure all callers invoking ImportLayer are prepared to handle a nil layer list.
  • Verify that downstream operations (e.g., iterating over the list or checking its length) do not introduce bugs.
  • Update documentation if returning nil is an intentional design choice, or consider returning an empty list if that better meets caller expectations.
server/internal/usecase/interactor/plugin_test.go (1)

33-33: LGTM! Scene initialization simplified.

The removal of root layer from scene initialization aligns with the broader refactoring effort while maintaining test functionality.

server/internal/adapter/gql/gqlmodel/models_gen.go (8)

186-186: Confirm optional vs mandatory project association.
In CreateAssetInput, the new ProjectID field is nullable. Confirm that business logic and validations handle the scenario where no project is specified. Otherwise, consider making the project association required.


906-915: Potential inconsistency with the ID field type.
Here, ID is defined as a standard string, whereas other similar fields rely on the custom ID alias. Confirm this distinction is intentional; otherwise, unify the types for consistency and clarity.


919-921: Check necessity of dataset references.
These fields reference datasets, yet related dataset code was removed in other parts of the codebase. Verify these definitions are still relevant or remove them if they are no longer needed.


1221-1222: New GA tracking fields in Story.
Adding enableGa and trackingId to the Story type appears correct. Ensure you have tests to confirm these fields function as expected and that front-end updates align with these new fields.


1332-1334: Introducing project linkage to asset updates.
The new ProjectID within UpdateAssetInput is consistent with the overall design. Double-check the resolver logic to handle optional vs. required project references properly.


1337-1339: Refined UpdateAssetPayload fields.
Returning both AssetID and ProjectID in the payload cleanly communicates the updated asset state. The design looks good.


1459-1460: Optional GA fields in update requests.
Making EnableGa and TrackingID pointers in UpdateStoryInput is a suitable approach for partial updates. Confirm you correctly distinguish between “no update requested” vs. “explicitly clear the field.”


1848-1848: Extended case statement for plugin extension types.
The updated case line appears to include additional plugin extension types. This is fine as long as the switch statement’s logic is consistent with the new extension coverage.

server/e2e/gql_me_test.go (1)

8-8: LGTM! Server initialization has been simplified.

The change from StartServer to Server streamlines the test setup while maintaining the test's functionality.

server/e2e/ping_test.go (1)

9-9: LGTM! Server initialization has been simplified.

The change from StartServer to ServerPingTest maintains the test's functionality while making the setup more concise.

server/internal/usecase/interfaces/scene.go (1)

25-25: Document the purpose of the new boolean parameter.

The Create method signature has been updated to include a boolean parameter, but its purpose is not documented. Please add documentation to clarify the parameter's role and expected behavior.

server/e2e/gql_scene_test.go (1)

16-16: LGTM! Response object access is more explicit.

The changes to use Object().Value() instead of direct Value() calls improve type safety and make the object traversal more explicit.

Also applies to: 42-42, 71-73

server/internal/adapter/gql/gqlmodel/convert_project.go (2)

108-113: LGTM!

The ProjectExport struct is well-designed with appropriate field types and JSON tags.


115-125: LGTM!

The function correctly handles nil input and properly maps all required fields.

server/internal/usecase/interactor/asset.go (3)

39-51: LGTM!

The function correctly handles permissions and passes all parameters to the repository layer.


91-91: LGTM!

The function correctly sets the project ID during asset creation.


154-195: LGTM!

The function correctly handles file imports and sets all required fields, including the project ID.

server/e2e/gql_project_export_import_test.go (1)

21-64: LGTM! Well-structured test with clear steps.

The test is organized into clear steps with descriptive comments, making it easy to understand and maintain. The test also properly cleans up by removing the exported file.

server/e2e/gql_project_test.go (2)

42-42: LGTM: Test expectation updated to match new data structure.

The test now expects 2 edges instead of 1, which aligns with the changes in the test data setup.


144-144: LGTM: Test expectation updated to match new data structure.

The test now expects 5 edges instead of 4, which aligns with the changes in the test data setup.

server/internal/usecase/interactor/scene.go (2)

674-677: Fix potential nil pointer dereference.

The code now safely handles the case where storyList is nil or empty, preventing potential runtime panics.


705-708: Improve error handling in asset export.

The code now:

  1. Properly handles URL parsing
  2. Uses AddZipAsset for consistent asset handling
  3. Logs errors without failing the export
server/e2e/gql_storytelling_test.go (2)

15-55: LGTM: Comprehensive test coverage for Story CRUD operations.

The test suite properly validates:

  1. Story creation and retrieval
  2. Story updates including GA parameters
  3. Story deletion

561-583:

✅ Verification successful

Verify GA parameters in story update mutation.

The mutation now includes enableGa and trackingId parameters, aligning with the PR objectives.

Run this script to ensure all story-related queries include the new GA fields:


🏁 Script executed:

#!/bin/bash
# Search for story-related GraphQL queries and verify GA fields
rg -l 'type Story' | xargs rg 'enableGa|trackingId'

Length of output: 30610


GA Parameters in Story Update Mutation Verified

The mutation in server/e2e/gql_storytelling_test.go now correctly includes the enableGa and trackingId fields. I ran a script to search for these parameters across the GraphQL models and generated code. The results confirm that both fields are present in all relevant story-related queries and mutations (e.g. in server/internal/adapter/gql/gqlmodel/models_gen.go, server/internal/adapter/gql/generated.go, and server/gql/storytelling.graphql).

No further changes are needed on this part of the code.

server/gql/scene.graphql (1)

50-52: LGTM: Improved schema formatting.

The added space after Query keyword improves readability and maintains consistent formatting.

Comment on lines 127 to 137
func ToProjectExportFromJSON(data map[string]any) *ProjectExport {
var p ProjectExport
bytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
return nil
}
if err := json.Unmarshal(bytes, &p); err != nil {
return nil
}
return &p
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider improving error handling and JSON conversion.

  1. Using json.MarshalIndent for conversion is unnecessary and less efficient than json.Marshal.
  2. Silently returning nil on errors makes debugging harder.

Apply this diff to improve the implementation:

 func ToProjectExportFromJSON(data map[string]any) *ProjectExport {
 	var p ProjectExport
-	bytes, err := json.MarshalIndent(data, "", "  ")
+	bytes, err := json.Marshal(data)
 	if err != nil {
-		return nil
+		return nil // Consider logging the error or returning it
 	}
 	if err := json.Unmarshal(bytes, &p); err != nil {
-		return nil
+		return nil // Consider logging the error or returning it
 	}
 	return &p
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func ToProjectExportFromJSON(data map[string]any) *ProjectExport {
var p ProjectExport
bytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
return nil
}
if err := json.Unmarshal(bytes, &p); err != nil {
return nil
}
return &p
}
func ToProjectExportFromJSON(data map[string]any) *ProjectExport {
var p ProjectExport
bytes, err := json.Marshal(data)
if err != nil {
return nil // Consider logging the error or returning it
}
if err := json.Unmarshal(bytes, &p); err != nil {
return nil // Consider logging the error or returning it
}
return &p
}

Comment on lines 15 to 16
func (r *queryResolver) Assets(ctx context.Context, teamID gqlmodel.ID, projectId *gqlmodel.ID, pagination *gqlmodel.Pagination, keyword *string, sortType *gqlmodel.AssetSort) (*gqlmodel.AssetConnection, error) {
return loaders(ctx).Asset.FindByWorkspace(ctx, teamID, projectId, keyword, gqlmodel.AssetSortTypeFrom(sortType), pagination)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix inconsistent function name.

The loader call uses FindByWorkspace but the implementation uses FindByWorkspaceProject.

Apply this diff to fix the inconsistency:

-	return loaders(ctx).Asset.FindByWorkspace(ctx, teamID, projectId, keyword, gqlmodel.AssetSortTypeFrom(sortType), pagination)
+	return loaders(ctx).Asset.FindByWorkspaceProject(ctx, teamID, projectId, keyword, gqlmodel.AssetSortTypeFrom(sortType), pagination)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (r *queryResolver) Assets(ctx context.Context, teamID gqlmodel.ID, projectId *gqlmodel.ID, pagination *gqlmodel.Pagination, keyword *string, sortType *gqlmodel.AssetSort) (*gqlmodel.AssetConnection, error) {
return loaders(ctx).Asset.FindByWorkspace(ctx, teamID, projectId, keyword, gqlmodel.AssetSortTypeFrom(sortType), pagination)
func (r *queryResolver) Assets(ctx context.Context, teamID gqlmodel.ID, projectId *gqlmodel.ID, pagination *gqlmodel.Pagination, keyword *string, sortType *gqlmodel.AssetSort) (*gqlmodel.AssetConnection, error) {
return loaders(ctx).Asset.FindByWorkspaceProject(ctx, teamID, projectId, keyword, gqlmodel.AssetSortTypeFrom(sortType), pagination)
}

Comment on lines 121 to 163
func addAsset(path string, ctx context.Context, r *repo.Container, gf gateway.File) (*url.URL, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer func() {
if cerr := f.Close(); cerr != nil && err == nil {
err = cerr
}
}()
stat, err := f.Stat()
if err != nil {
return nil, err
}
contentType := mime.TypeByExtension(filepath.Ext(path))
if contentType == "" {
contentType = "application/octet-stream"
}

u, size, err := gf.UploadAsset(ctx, &file.File{
Content: f,
Path: path,
Size: int64(stat.Size()),
ContentType: contentType,
})
if err != nil {
return nil, err
}

aid := asset.NewID()
a := asset.New().
ID(aid).
Workspace(wID).
Project(&pID).
URL(u.String()).
CreatedAt(aid.Timestamp()).
Name(path).
Size(size).
ContentType(contentType).
CoreSupport(true).
MustBuild()
if err := r.DatasetSchema.Save(ctx, dss); err != nil {
return u, r.Asset.Save(ctx, a)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for asset file size and content type.

The function should validate file size and content type before uploading.

 func addAsset(path string, ctx context.Context, r *repo.Container, gf gateway.File) (*url.URL, error) {
     f, err := os.Open(path)
     if err != nil {
         return nil, err
     }
     defer func() {
         if cerr := f.Close(); cerr != nil && err == nil {
             err = cerr
         }
     }()
     stat, err := f.Stat()
     if err != nil {
         return nil, err
     }
+    // Validate file size (e.g., max 10MB)
+    if stat.Size() > 10*1024*1024 {
+        return nil, fmt.Errorf("file size %d exceeds maximum allowed size of 10MB", stat.Size())
+    }
+
     contentType := mime.TypeByExtension(filepath.Ext(path))
     if contentType == "" {
         contentType = "application/octet-stream"
     }
+    // Validate content type
+    allowedTypes := map[string]bool{
+        "image/jpeg": true,
+        "image/png":  true,
+        "image/gif":  true,
+    }
+    if !allowedTypes[contentType] {
+        return nil, fmt.Errorf("unsupported content type: %s", contentType)
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func addAsset(path string, ctx context.Context, r *repo.Container, gf gateway.File) (*url.URL, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer func() {
if cerr := f.Close(); cerr != nil && err == nil {
err = cerr
}
}()
stat, err := f.Stat()
if err != nil {
return nil, err
}
contentType := mime.TypeByExtension(filepath.Ext(path))
if contentType == "" {
contentType = "application/octet-stream"
}
u, size, err := gf.UploadAsset(ctx, &file.File{
Content: f,
Path: path,
Size: int64(stat.Size()),
ContentType: contentType,
})
if err != nil {
return nil, err
}
aid := asset.NewID()
a := asset.New().
ID(aid).
Workspace(wID).
Project(&pID).
URL(u.String()).
CreatedAt(aid.Timestamp()).
Name(path).
Size(size).
ContentType(contentType).
CoreSupport(true).
MustBuild()
if err := r.DatasetSchema.Save(ctx, dss); err != nil {
return u, r.Asset.Save(ctx, a)
}
func addAsset(path string, ctx context.Context, r *repo.Container, gf gateway.File) (*url.URL, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer func() {
if cerr := f.Close(); cerr != nil && err == nil {
err = cerr
}
}()
stat, err := f.Stat()
if err != nil {
return nil, err
}
// Validate file size (e.g., max 10MB)
if stat.Size() > 10*1024*1024 {
return nil, fmt.Errorf("file size %d exceeds maximum allowed size of 10MB", stat.Size())
}
contentType := mime.TypeByExtension(filepath.Ext(path))
if contentType == "" {
contentType = "application/octet-stream"
}
// Validate content type
allowedTypes := map[string]bool{
"image/jpeg": true,
"image/png": true,
"image/gif": true,
}
if !allowedTypes[contentType] {
return nil, fmt.Errorf("unsupported content type: %s", contentType)
}
u, size, err := gf.UploadAsset(ctx, &file.File{
Content: f,
Path: path,
Size: int64(stat.Size()),
ContentType: contentType,
})
if err != nil {
return nil, err
}
aid := asset.NewID()
a := asset.New().
ID(aid).
Workspace(wID).
Project(&pID).
URL(u.String()).
CreatedAt(aid.Timestamp()).
Name(path).
Size(size).
ContentType(contentType).
CoreSupport(true).
MustBuild()
return u, r.Asset.Save(ctx, a)
}

Comment on lines 627 to 649
func AddZipAsset(ctx context.Context, file gateway.File, zipWriter *zip.Writer, path string) error {
fileName := strings.TrimPrefix(path, "/assets/")
stream, err := file.ReadAsset(ctx, fileName)
if err != nil {
return nil // skip if external URL
}
defer func() {
if cerr := stream.Close(); cerr != nil {
fmt.Printf("Error closing file: %v\n", cerr)
}
}()
zipEntryPath := fmt.Sprintf("assets/%s", fileName)
zipEntry, err := zipWriter.Create(zipEntryPath)
if err != nil {
return err
}
_, err = io.Copy(zipEntry, stream)
if err != nil {
_ = stream.Close()
return err
}
return nil
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling in AddZipAsset.

The function should handle errors more robustly and provide better error messages.

 func AddZipAsset(ctx context.Context, file gateway.File, zipWriter *zip.Writer, path string) error {
+    if file == nil || zipWriter == nil {
+        return errors.New("file and zipWriter must not be nil")
+    }
+    if path == "" {
+        return errors.New("path must not be empty")
+    }
+
     fileName := strings.TrimPrefix(path, "/assets/")
     stream, err := file.ReadAsset(ctx, fileName)
     if err != nil {
-        return nil // skip if external URL
+        // Log skipped external URLs for debugging
+        fmt.Printf("Skipping external URL: %s\n", path)
+        return nil
     }
     defer func() {
         if cerr := stream.Close(); cerr != nil {
-            fmt.Printf("Error closing file: %v\n", cerr)
+            fmt.Printf("Warning: Failed to close stream for %s: %v\n", fileName, cerr)
         }
     }()
     zipEntryPath := fmt.Sprintf("assets/%s", fileName)
     zipEntry, err := zipWriter.Create(zipEntryPath)
     if err != nil {
-        return err
+        return fmt.Errorf("failed to create zip entry %s: %w", zipEntryPath, err)
     }
     _, err = io.Copy(zipEntry, stream)
     if err != nil {
         _ = stream.Close()
-        return err
+        return fmt.Errorf("failed to copy asset %s to zip: %w", fileName, err)
     }
     return nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func AddZipAsset(ctx context.Context, file gateway.File, zipWriter *zip.Writer, path string) error {
fileName := strings.TrimPrefix(path, "/assets/")
stream, err := file.ReadAsset(ctx, fileName)
if err != nil {
return nil // skip if external URL
}
defer func() {
if cerr := stream.Close(); cerr != nil {
fmt.Printf("Error closing file: %v\n", cerr)
}
}()
zipEntryPath := fmt.Sprintf("assets/%s", fileName)
zipEntry, err := zipWriter.Create(zipEntryPath)
if err != nil {
return err
}
_, err = io.Copy(zipEntry, stream)
if err != nil {
_ = stream.Close()
return err
}
return nil
}
func AddZipAsset(ctx context.Context, file gateway.File, zipWriter *zip.Writer, path string) error {
if file == nil || zipWriter == nil {
return errors.New("file and zipWriter must not be nil")
}
if path == "" {
return errors.New("path must not be empty")
}
fileName := strings.TrimPrefix(path, "/assets/")
stream, err := file.ReadAsset(ctx, fileName)
if err != nil {
// Log skipped external URLs for debugging
fmt.Printf("Skipping external URL: %s\n", path)
return nil
}
defer func() {
if cerr := stream.Close(); cerr != nil {
fmt.Printf("Warning: Failed to close stream for %s: %v\n", fileName, cerr)
}
}()
zipEntryPath := fmt.Sprintf("assets/%s", fileName)
zipEntry, err := zipWriter.Create(zipEntryPath)
if err != nil {
return fmt.Errorf("failed to create zip entry %s: %w", zipEntryPath, err)
}
_, err = io.Copy(zipEntry, stream)
if err != nil {
return fmt.Errorf("failed to copy asset %s to zip: %w", fileName, err)
}
return nil
}

@hexaforce hexaforce force-pushed the feat/storytelling-ga-support branch from edc304a to 261978e Compare February 20, 2025 08:41
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (4)
server/internal/adapter/gql/resolver_mutation_project.go (2)

214-214: ⚠️ Potential issue

Handle potential error during workspace ID conversion.

The workspace ID conversion ignores potential errors, which could lead to runtime issues.

- workspace, _ := accountdomain.WorkspaceIDFrom(string(input.TeamID))
+ workspace, err := accountdomain.WorkspaceIDFrom(string(input.TeamID))
+ if err != nil {
+     return nil, errors.New("invalid team ID")
+ }

224-224: ⚠️ Potential issue

Handle potential error from unmarshalProject.

The unmarshalProject error is ignored, which could lead to nil dereference if unmarshaling fails.

- projectData, _ := unmarshalProject(tempData)
+ projectData, err := unmarshalProject(tempData)
+ if err != nil {
+     return nil, errors.New("failed to unmarshal project data: " + err.Error())
+ }
server/e2e/common.go (1)

266-271: ⚠️ Potential issue

Add type assertion error handling in JSONEqRegexpValue.

The type assertion in JSONEqRegexpValue should handle the case where the assertion fails.

Apply this diff:

 func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
-    if actualData, ok := actual.Raw().(map[string]interface{}); ok {
+    actualData, ok := actual.Raw().(map[string]interface{})
+    if !ok {
+        t.Errorf("expected map[string]interface{}, got %T", actual.Raw())
+        return false
+    }
     return JSONEqRegexpInterface(t, actualData, expected)
-    }
-    return false
 }
server/e2e/gql_asset_test.go (1)

224-240: ⚠️ Potential issue

Add error handling for updateAsset response.

The function should check for errors in the GraphQL response.

Apply this diff:

 func updateAsset(e *httpexpect.Expect, assetId string, projectId *string) *httpexpect.Value {
     requestBody := GraphQLRequest{
         OperationName: "UpdateAsset",
         Query: `mutation UpdateAsset($assetId: ID!, $projectId: ID) {
             updateAsset(input: { assetId: $assetId projectId: $projectId }) {
                 assetId
                 projectId
+                errors {
+                    message
+                    path
+                }
                 __typename
             }
         }`,
         Variables: map[string]any{
             "assetId":   assetId,
             "projectId": projectId,
         },
     }
     return Request(e, uID.String(), requestBody)
 }
🧹 Nitpick comments (5)
server/e2e/gql_storytelling_test.go (2)

15-55: LGTM! Test coverage for GA fields looks good.

The test successfully verifies the new Google Analytics fields. The update operation correctly sets and verifies both enableGa and trackingId.

Consider adding test cases for:

  • Updating GA fields independently
  • Edge cases with empty tracking ID
  • Disabling GA after enabling

232-413: Add test cases for GA fields in published stories.

While the test verifies the default values for GA fields (enableGa: false and trackingId: ""), it would be beneficial to test publishing with GA enabled and a tracking ID set.

Add a test case that:

  1. Updates the story with GA enabled and a tracking ID
  2. Publishes the story
  3. Verifies the published JSON contains the updated GA values
server/internal/adapter/gql/gqlmodel/models_gen.go (1)

1199-1223: Consider adding validation for Google Analytics tracking ID.

While the addition of enableGa and trackingId fields aligns with the PR objectives, consider:

  1. Making trackingId optional since it's only relevant when enableGa is true
  2. Adding validation to ensure the tracking ID follows Google Analytics format (e.g., "UA-XXXXX-Y" or "G-XXXXXXXX")
type Story struct {
  // ... other fields ...
-  TrackingID        string            `json:"trackingId"`
+  TrackingID        *string           `json:"trackingId"`
}
server/internal/usecase/interactor/scene.go (1)

699-701: Consider refactoring repeated URL asset handling logic.

There are multiple instances of similar URL parsing and asset handling code. Consider extracting this into a helper function to improve maintainability and reduce duplication.

+func addURLAssetToZip(ctx context.Context, file gateway.File, zipWriter *zip.Writer, value any) error {
+    if u, ok := value.(*url.URL); ok {
+        return AddZipAsset(ctx, file, zipWriter, u.Path)
+    }
+    return nil
+}

Then use it in the existing code:

-if u, ok := field.Value().Value().(*url.URL); ok {
-    if err := AddZipAsset(ctx, i.file, zipWriter, u.Path); err != nil {
-        log.Infofc(ctx, "Fail widget addZipAsset :", err.Error())
-    }
-}
+if err := addURLAssetToZip(ctx, i.file, zipWriter, field.Value().Value()); err != nil {
+    log.Infofc(ctx, "Fail widget addZipAsset :", err.Error())
+}

Also applies to: 728-730, 753-755

server/internal/adapter/gql/resolver_mutation_project.go (1)

178-182: Remove commented-out code.

The commented-out code block should be removed as it's no longer needed and may cause confusion.

-	// pid2, err := gqlmodel.ToID[id.Project](gqlmodel.ID(prj.ID().String()))
-	// if err != nil {
-	// 	return nil, err
-	// }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between edc304a and 261978e.

📒 Files selected for processing (20)
  • server/e2e/common.go (7 hunks)
  • server/e2e/gql_asset_test.go (6 hunks)
  • server/e2e/gql_me_test.go (1 hunks)
  • server/e2e/gql_project_test.go (6 hunks)
  • server/e2e/gql_storytelling_test.go (6 hunks)
  • server/e2e/mock_test.go (1 hunks)
  • server/e2e/ping_test.go (1 hunks)
  • server/gql/storytelling.graphql (3 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (9 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (6 hunks)
  • server/internal/adapter/gql/resolver_mutation_scene.go (0 hunks)
  • server/internal/adapter/gql/resolver_mutation_storytelling.go (1 hunks)
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go (4 hunks)
  • server/internal/usecase/interactor/scene.go (6 hunks)
  • server/internal/usecase/interactor/storytelling.go (1 hunks)
  • server/internal/usecase/interactor/storytelling_test.go (2 hunks)
  • server/internal/usecase/interfaces/story.go (1 hunks)
  • server/pkg/storytelling/story.go (4 hunks)
  • server/pkg/storytelling/story_bulider.go (1 hunks)
💤 Files with no reviewable changes (1)
  • server/internal/adapter/gql/resolver_mutation_scene.go
🚧 Files skipped from review as they are similar to previous changes (11)
  • server/internal/adapter/gql/resolver_mutation_storytelling.go
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go
  • server/internal/usecase/interfaces/story.go
  • server/internal/usecase/interactor/storytelling.go
  • server/pkg/storytelling/story_bulider.go
  • server/internal/usecase/interactor/storytelling_test.go
  • server/e2e/mock_test.go
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go
  • server/e2e/gql_project_test.go
  • server/gql/storytelling.graphql
  • server/pkg/storytelling/story.go
🧰 Additional context used
🧠 Learnings (1)
server/internal/adapter/gql/resolver_mutation_project.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:0-0
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project export functionality, the ZIP file is deleted after saving to storage.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:215-217
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project import functionality, the file names are unique and can be safely replaced uniformly using `bytes.Replace`.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: deploy-server / deploy_test
🔇 Additional comments (14)
server/e2e/gql_storytelling_test.go (1)

513-514: LGTM! GraphQL schema changes are well implemented.

The GA fields have been correctly added to both the query and mutation operations. The implementation follows GraphQL best practices with proper typing.

Also applies to: 564-565, 581-582

server/internal/adapter/gql/gqlmodel/models_gen.go (2)

134-145: LGTM! Asset-Project association is well structured.

The addition of the optional ProjectID field to Asset types enables proper organization and management of assets within projects. The changes maintain consistency across Asset-related types.

Also applies to: 184-189, 1332-1340


829-856: Clarify precedence of Google Analytics settings.

The Google Analytics settings exist at both Project and Story levels. This might cause confusion about which settings take precedence when both are configured.

Consider:

  1. Documenting the precedence rules
  2. Adding validation for the tracking ID format
  3. Making trackingId optional in the Project type as well
type Project struct {
  // ... other fields ...
-  TrackingID        string            `json:"trackingId"`
+  TrackingID        *string           `json:"trackingId"`
}

Also applies to: 1397-1418

server/internal/usecase/interactor/scene.go (3)

21-21: LGTM: Import addition aligns with Story GA support.

The addition of the storytelling package import is consistent with the PR's objective of adding Google Analytics support to the Story component.


150-151: LGTM: Improved property access control.

The use of writableFilter enhances security by ensuring property writes are properly scoped to the specific scene.


668-671: LGTM: Safe story data handling.

The code safely handles potential nil cases and array bounds for story data, preventing potential runtime panics.

server/internal/adapter/gql/resolver_mutation_project.go (3)

83-84: LGTM: GA support fields added correctly.

The addition of EnableGa and TrackingID fields aligns with the PR objectives and follows the existing parameter pattern.


189-194: LGTM: Improved URL parsing with proper error handling.

The addition of proper URL parsing with error handling enhances the robustness of the asset name extraction process.


229-233: LGTM: Proper transaction handling.

The transaction handling follows best practices with proper error propagation and cleanup in the defer block.

server/e2e/gql_me_test.go (1)

8-8: LGTM! Server initialization simplified.

The change streamlines the server initialization by using the new Server() function while maintaining the test's functionality.

server/e2e/ping_test.go (1)

9-9: LGTM! Server initialization simplified.

The change streamlines the server initialization by using the new ServerPingTest() function while maintaining the test's functionality.

server/e2e/common.go (2)

35-43: LGTM! Good configuration refactoring.

Moving common configuration into variables improves code reusability and maintainability.


283-297: LGTM! Improved ValueDump function.

The function now handles different types more robustly with proper type assertions and reflection for unsupported types.

server/e2e/gql_asset_test.go (1)

124-179: LGTM! Comprehensive project association tests.

The test cases thoroughly validate asset-project associations, including:

  • Asset creation with project association
  • Asset retrieval by project
  • Moving assets between projects
  • Removing project associations
  • Asset deletion

Comment on lines 359 to 368
func unmarshalAssets(data []byte) (map[string]string, error) {
var jsonData map[string]interface{}
if err := json.Unmarshal(data, &jsonData); err != nil {
return nil, err
}

assets, _ := jsonData["assets"].(map[string]interface{})
assetNames := make(map[string]string)
for k, v := range assets {
strValue, _ := v.(string)
assetNames[k] = strValue
}

return assetNames, nil
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Standardize error handling in unmarshal functions.

The unmarshal helper functions have inconsistent error handling. Type assertions should be checked consistently across all functions.

Example for unmarshalAssets:

 func unmarshalAssets(data []byte) (map[string]string, error) {
     var jsonData map[string]interface{}
     if err := json.Unmarshal(data, &jsonData); err != nil {
         return nil, err
     }

-    assets, _ := jsonData["assets"].(map[string]interface{})
+    assets, ok := jsonData["assets"].(map[string]interface{})
+    if !ok {
+        return nil, errors.New("invalid assets data format")
+    }
     assetNames := make(map[string]string)
     for k, v := range assets {
-        strValue, _ := v.(string)
+        strValue, ok := v.(string)
+        if !ok {
+            return nil, fmt.Errorf("invalid asset name format for key %s", k)
+        }
         assetNames[k] = strValue
     }

     return assetNames, nil
 }

Apply similar changes to unmarshalPluginsScene.

Also applies to: 375-385

Comment on lines 242 to 256
func removeAsset(e *httpexpect.Expect, assetId string) *httpexpect.Value {
requestBody := GraphQLRequest{
OperationName: "RemoveAsset",
Query: `mutation RemoveAsset($assetId: ID!) {
removeAsset(input: { assetId: $assetId }) {
assetId
__typename
}
}`,
Variables: map[string]any{
"assetId": assetId,
},
}
return Request(e, uID.String(), requestBody)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for removeAsset response.

Similar to updateAsset, this function should also check for errors.

Apply this diff:

 func removeAsset(e *httpexpect.Expect, assetId string) *httpexpect.Value {
     requestBody := GraphQLRequest{
         OperationName: "RemoveAsset",
         Query: `mutation RemoveAsset($assetId: ID!) {
             removeAsset(input: { assetId: $assetId }) {
                 assetId
+                errors {
+                    message
+                    path
+                }
                 __typename
             }
         }`,
         Variables: map[string]any{
             "assetId": assetId,
         },
     }
     return Request(e, uID.String(), requestBody)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func removeAsset(e *httpexpect.Expect, assetId string) *httpexpect.Value {
requestBody := GraphQLRequest{
OperationName: "RemoveAsset",
Query: `mutation RemoveAsset($assetId: ID!) {
removeAsset(input: { assetId: $assetId }) {
assetId
__typename
}
}`,
Variables: map[string]any{
"assetId": assetId,
},
}
return Request(e, uID.String(), requestBody)
}
func removeAsset(e *httpexpect.Expect, assetId string) *httpexpect.Value {
requestBody := GraphQLRequest{
OperationName: "RemoveAsset",
Query: `mutation RemoveAsset($assetId: ID!) {
removeAsset(input: { assetId: $assetId }) {
assetId
errors {
message
path
}
__typename
}
}`,
Variables: map[string]any{
"assetId": assetId,
},
}
return Request(e, uID.String(), requestBody)
}

@hexaforce hexaforce force-pushed the feat/storytelling-ga-support branch 2 times, most recently from 10e3df1 to e394704 Compare February 25, 2025 02:45
@github-actions github-actions bot removed the web label Feb 25, 2025
@hexaforce hexaforce force-pushed the feat/storytelling-ga-support branch from e394704 to 6f6caec Compare February 25, 2025 02:53
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
server/e2e/gql_project_export_import_test.go (1)

29-29: 🛠️ Refactor suggestion

Add error handling for HTTP response in exporProject.

The function should check the HTTP response status and handle potential errors before writing to file.

 downloadResponse := e.GET(fmt.Sprintf("http://localhost:8080%s", downloadPath)).
   Expect().
   Status(http.StatusOK).
   Body().Raw()
+if len(downloadResponse) == 0 {
+  t.Fatal("Empty response from export endpoint")
+}
 fileName := "project_data.zip"
server/e2e/seeder.go (2)

203-205: ⚠️ Potential issue

Fix critical error in conditional logic.

The error check in this line is incorrect. err != err will always evaluate to false, making the error handling code unreachable.

Apply this diff to fix the issue:

- if err = r.Scene.Save(ctx, s); err != err {
+ if err = r.Scene.Save(ctx, s); err != nil {
  return err
}

206-208: ⚠️ Potential issue

Fix all remaining incorrect error check conditions.

Similarly, all of these error checks use err != err which is logically incorrect and will never evaluate to true. This causes the error handling code to be unreachable.

Fix all instances by replacing with err != nil:

- if err != err {
+ if err != nil {
   return err
}

Also applies to: 209-211, 212-214, 215-217, 218-220, 221-223, 266-268, 269-271, 297-299, 312-314, 350-352

🧹 Nitpick comments (12)
server/internal/app/app.go (2)

72-78: Improved auth configuration with conditional middleware

The changes to the authentication logic are well-structured, adding support for disabling authentication server while maintaining the existing JWT provider functionality. This approach allows for more flexibility in development and testing environments.

However, I notice that the auth config is logged twice - once here and again on line 138. Consider consolidating these log statements to avoid duplication.

- authConfig := cfg.Config.JWTProviders()
- log.Infof("auth: config: %#v", authConfig)
+ authConfig := cfg.Config.JWTProviders()

And modify line 138 to include both pieces of information:

- log.Infofc(ctx, "auth: config: %#v", cfg.Config.AuthSrv)
+ log.Infofc(ctx, "auth: server config: %#v, jwt providers: %#v", cfg.Config.AuthSrv, authConfig)

164-168: Simple but effective dummy auth middleware implementation

This implementation correctly provides a pass-through middleware when authentication is disabled. The function signature matches what's expected by the caller, and the implementation is minimal yet functional.

Since this is a security-related feature, consider adding a comment explaining that this middleware should only be used in development/testing environments where security isn't a concern.

func AuthMiddlewareDummy() (func(http.Handler) http.Handler, error) {
+	// WARNING: This middleware bypasses authentication entirely and should only be used
+	// in controlled environments like testing or local development where security isn't a concern.
	return func(next http.Handler) http.Handler {
		return next
	}, nil
}
server/internal/adapter/gql/resolver_mutation_project.go (1)

177-182: Remove commented-out code.

This commented-out code block is unnecessary and should be removed to maintain code cleanliness.

-// pid2, err := gqlmodel.ToID[id.Project](gqlmodel.ID(prj.ID().String()))
-// if err != nil {
-// 	return nil, err
-// }
server/internal/usecase/interactor/project.go (1)

627-649: Improve error handling in AddZipAsset.

The function should handle errors more robustly and provide better error messages.

 func AddZipAsset(ctx context.Context, file gateway.File, zipWriter *zip.Writer, path string) error {
+    if file == nil || zipWriter == nil {
+        return errors.New("file or zipWriter is nil")
+    }
+    if path == "" {
+        return errors.New("asset path cannot be empty")
+    }
+
     fileName := strings.TrimPrefix(path, "/assets/")
     stream, err := file.ReadAsset(ctx, fileName)
     if err != nil {
-        return nil // skip if external URL
+        // Skip external URLs but log for debugging
+        return nil
     }
     defer func() {
         if cerr := stream.Close(); cerr != nil {
-            fmt.Printf("Error closing file: %v\n", cerr)
+            fmt.Printf("Warning: Failed to close stream for %s: %v\n", fileName, cerr)
         }
     }()
     zipEntryPath := fmt.Sprintf("assets/%s", fileName)
     zipEntry, err := zipWriter.Create(zipEntryPath)
     if err != nil {
-        return err
+        return fmt.Errorf("failed to create zip entry %s: %w", zipEntryPath, err)
     }
     _, err = io.Copy(zipEntry, stream)
     if err != nil {
         _ = stream.Close()
-        return err
+        return fmt.Errorf("failed to copy asset %s to zip: %w", fileName, err)
     }
     return nil
 }
server/e2e/gql_project_export_import_test.go (4)

66-70: Consider adding error handling for JSON unmarshaling failures.

The function assumes the value can always be marshaled into JSON. If the value is nil or otherwise invalid, this could panic.

 func convertLine(t *testing.T, key string, v *httpexpect.Value) []string {
+  if v == nil || v.Raw() == nil {
+    t.Fatalf("value for key %s is nil", key)
+    return []string{}
+  }
   v2, err := json.MarshalIndent(v.Object().Value(key).Raw(), "", "  ")
   assert.Nil(t, err)
   return strings.Split(strings.ReplaceAll(string(v2), "\r\n", "\n"), "\n")
 }

72-102: Consider using a structured comparison library for deeper JSON comparison.

The current line-by-line comparison approach with various exceptions is functional but can be brittle. Consider using a library designed for JSON comparison with customizable rules.

The current implementation works well for this specific test case but might be less maintainable as the data structures change. A structured comparison library would provide more flexibility and better error messages.


113-120: Broaden ID validation to support multiple ID types.

The current implementation only validates against id.Widget type. Consider expanding this to support other ID types used in the application.

 func isID(text string) bool {
   v := strings.TrimSpace(text)
   v = strings.Trim(v, `"`)
-  if _, err := idx.From[id.Widget](v); err != nil {
+  // Try parsing as different ID types
+  if _, err := idx.From[id.Widget](v); err == nil {
+    return true
+  }
+  if _, err := idx.From[id.Scene](v); err == nil {
+    return true
+  }
+  if _, err := idx.From[id.Story](v); err == nil {
     return true
   }
-  return true
+  return false
 }

122-141: Ensure zip file creation doesn't conflict with existing files.

The current implementation creates a file with a hardcoded name, which could cause issues in parallel test execution.

-  fileName := "project_data.zip"
+  fileName := fmt.Sprintf("project_data_%s.zip", p) // Use project ID to make filename unique
   err := os.WriteFile(fileName, []byte(downloadResponse), os.ModePerm)
   assert.Nil(t, err)
   return fileName
server/e2e/gql_storytelling_test.go (1)

232-413: Consider using a more maintainable approach for complex JSON assertions.

The current test uses a large string with regular expressions for dynamic values, which could become brittle if the JSON structure changes. Consider using structured assertions or a JSON schema validation approach.

This test is comprehensive but might be difficult to maintain. Consider breaking it down into smaller, more focused assertions or using a JSON schema validation library.

server/internal/adapter/gql/generated.go (2)

8162-8162: Inserted comment marker # Publishment.
No functional change.


56783-56786: Root resolver references updateAsset in mutations.
Ensure consistent testing coverage.

server/e2e/seeder.go (1)

507-522: Consider using a standard UUID library.

While your manual UUID generation follows the UUID v4 specifications, consider using a standard library like github.com/google/uuid for better maintainability and RFC 4122 compliance.

- func generateUUID() string {
-   b := make([]byte, 16)
-   _, err := rand.Read(b)
-   if err != nil {
-     panic(err)
-   }
-   b[6] = (b[6] & 0x0F) | 0x40
-   b[8] = (b[8] & 0x3F) | 0x80
-   return fmt.Sprintf("%s-%s-%s-%s-%s",
-     hex.EncodeToString(b[0:4]),
-     hex.EncodeToString(b[4:6]),
-     hex.EncodeToString(b[6:8]),
-     hex.EncodeToString(b[8:10]),
-     hex.EncodeToString(b[10:16]),
-   )
- }

+ import "github.com/google/uuid"
+
+ func generateUUID() string {
+   return uuid.New().String()
+ }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 261978e and 67ded98.

⛔ Files ignored due to path filters (1)
  • server/e2e/test.zip is excluded by !**/*.zip
📒 Files selected for processing (58)
  • server/e2e/common.go (7 hunks)
  • server/e2e/gql_asset_test.go (6 hunks)
  • server/e2e/gql_me_test.go (1 hunks)
  • server/e2e/gql_project_export_import_test.go (3 hunks)
  • server/e2e/gql_project_export_test.go (0 hunks)
  • server/e2e/gql_project_test.go (6 hunks)
  • server/e2e/gql_scene_test.go (4 hunks)
  • server/e2e/gql_storytelling_test.go (6 hunks)
  • server/e2e/gql_user_test.go (2 hunks)
  • server/e2e/gql_validate_geojson_test.go (1 hunks)
  • server/e2e/mock_test.go (1 hunks)
  • server/e2e/ping_test.go (1 hunks)
  • server/e2e/seeder.go (5 hunks)
  • server/go.mod (1 hunks)
  • server/gql/asset.graphql (5 hunks)
  • server/gql/storytelling.graphql (3 hunks)
  • server/gql/workspace.graphql (2 hunks)
  • server/gqlgen.yml (0 hunks)
  • server/internal/adapter/gql/generated.go (61 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go (5 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project.go (2 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (5 hunks)
  • server/internal/adapter/gql/loader_asset.go (2 hunks)
  • server/internal/adapter/gql/resolver_mutation_asset.go (3 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (6 hunks)
  • server/internal/adapter/gql/resolver_mutation_storytelling.go (1 hunks)
  • server/internal/adapter/gql/resolver_query.go (1 hunks)
  • server/internal/adapter/gql/resolver_team.go (1 hunks)
  • server/internal/app/app.go (2 hunks)
  • server/internal/app/auth_client.go (1 hunks)
  • server/internal/infrastructure/fs/file.go (2 hunks)
  • server/internal/infrastructure/fs/file_test.go (3 hunks)
  • server/internal/infrastructure/memory/asset.go (1 hunks)
  • server/internal/infrastructure/mongo/asset.go (2 hunks)
  • server/internal/infrastructure/mongo/mongodoc/asset.go (3 hunks)
  • server/internal/infrastructure/mongo/mongodoc/scene.go (0 hunks)
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go (4 hunks)
  • server/internal/usecase/interactor/asset.go (6 hunks)
  • server/internal/usecase/interactor/nlslayer.go (3 hunks)
  • server/internal/usecase/interactor/project.go (5 hunks)
  • server/internal/usecase/interactor/project_test.go (0 hunks)
  • server/internal/usecase/interactor/scene.go (5 hunks)
  • server/internal/usecase/interactor/storytelling.go (2 hunks)
  • server/internal/usecase/interactor/storytelling_test.go (1 hunks)
  • server/internal/usecase/interfaces/asset.go (2 hunks)
  • server/internal/usecase/interfaces/project.go (2 hunks)
  • server/internal/usecase/interfaces/story.go (1 hunks)
  • server/internal/usecase/repo/asset.go (1 hunks)
  • server/pkg/asset/asset.go (2 hunks)
  • server/pkg/asset/builder.go (1 hunks)
  • server/pkg/asset/id.go (1 hunks)
  • server/pkg/nlslayer/feature.go (0 hunks)
  • server/pkg/nlslayer/feature_test.go (1 hunks)
  • server/pkg/scene/builder/story.go (1 hunks)
  • server/pkg/storytelling/story.go (4 hunks)
  • server/pkg/storytelling/story_bulider.go (1 hunks)
💤 Files with no reviewable changes (5)
  • server/internal/usecase/interactor/project_test.go
  • server/gqlgen.yml
  • server/e2e/gql_project_export_test.go
  • server/pkg/nlslayer/feature.go
  • server/internal/infrastructure/mongo/mongodoc/scene.go
🚧 Files skipped from review as they are similar to previous changes (39)
  • server/pkg/asset/builder.go
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go
  • server/internal/adapter/gql/resolver_mutation_storytelling.go
  • server/internal/usecase/interfaces/story.go
  • server/e2e/mock_test.go
  • server/internal/adapter/gql/resolver_query.go
  • server/go.mod
  • server/e2e/gql_user_test.go
  • server/pkg/storytelling/story_bulider.go
  • server/internal/usecase/interactor/storytelling_test.go
  • server/pkg/nlslayer/feature_test.go
  • server/internal/adapter/gql/gqlmodel/convert_asset.go
  • server/e2e/gql_me_test.go
  • server/gql/storytelling.graphql
  • server/internal/infrastructure/fs/file.go
  • server/pkg/scene/builder/story.go
  • server/pkg/asset/id.go
  • server/internal/usecase/repo/asset.go
  • server/internal/infrastructure/fs/file_test.go
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go
  • server/internal/adapter/gql/loader_asset.go
  • server/e2e/gql_validate_geojson_test.go
  • server/internal/app/auth_client.go
  • server/pkg/asset/asset.go
  • server/e2e/gql_scene_test.go
  • server/e2e/gql_project_test.go
  • server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go
  • server/internal/usecase/interfaces/project.go
  • server/internal/adapter/gql/resolver_team.go
  • server/internal/infrastructure/mongo/mongodoc/asset.go
  • server/gql/asset.graphql
  • server/internal/usecase/interactor/storytelling.go
  • server/internal/usecase/interactor/nlslayer.go
  • server/pkg/storytelling/story.go
  • server/internal/infrastructure/memory/asset.go
  • server/gql/workspace.graphql
  • server/internal/usecase/interfaces/asset.go
  • server/internal/adapter/gql/gqlmodel/models_gen.go
  • server/e2e/common.go
🧰 Additional context used
🧠 Learnings (2)
server/internal/adapter/gql/resolver_mutation_project.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:0-0
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project export functionality, the ZIP file is deleted after saving to storage.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:215-217
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project import functionality, the file names are unique and can be safely replaced uniformly using `bytes.Replace`.
server/e2e/gql_project_export_import_test.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:48-80
Timestamp: 2024-11-12T15:21:04.151Z
Learning: In `server/e2e/gql_import_export_test.go`, test functions like `TestCallImportProject` may have disabled assertions due to problems with GraphQL.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:12-44
Timestamp: 2024-11-12T15:21:04.150Z
Learning: The response body assertions in `TestCallExportProject` are disabled due to existing problems with GraphQL.
🔇 Additional comments (104)
server/e2e/ping_test.go (1)

9-9:

✅ Verification successful

Server initialization refactored

The server initialization has been simplified by using the ServerPingTest function instead of the previous explicit configuration. This improves code readability and maintainability. Make sure that ServerPingTest correctly sets up the CORS configuration to allow "https://example.com" as expected by the test.


🏁 Script executed:

#!/bin/bash
# Check the implementation of ServerPingTest to verify CORS configuration
grep -r "ServerPingTest" server/e2e/ --include="*.go" -A 10

Length of output: 1164


Server Initialization Refactored – CORS Configuration Confirmed

The refactoring to use ServerPingTest is verified to be working as expected. The test in server/e2e/ping_test.go correctly sets and validates the CORS configuration by checking that requests with the "Origin" header of "https://example.com" receive the appropriate "Access-Control-Allow-Origin" header. The implementation in server/e2e/common.go shows that the server is properly initialized with the expected origins configuration. No further changes are necessary.

server/internal/app/app.go (1)

72-78:

❓ Verification inconclusive

Verify security implications of disabled auth server

While the implementation is technically correct, disabling the auth server could have significant security implications. Ensure that this feature is properly documented and that there are appropriate safeguards to prevent it from being enabled in production environments.


🏁 Script executed:

#!/bin/bash
# Check if there are any tests that verify the auth server can't be disabled in production
rg -A 3 -B 3 "AuthSrv.Disabled" --glob "*.go" | grep -i "prod\|environment\|env"

# Check for any configuration validation that prevents disabling auth in production
rg -A 5 "ValidateConfig\|validateConfig\|ConfigValidation" --glob "*.go" | grep -A 5 -i "auth"

Length of output: 175


Security Implications of Disabled Authentication in Production

After executing our initial search scripts, we found no evidence of tests or configuration validations that ensure the auth server cannot be accidentally disabled in production. This raises potential security concerns regarding the use of the AuthSrv.Disabled flag.

  • Ensure Production Safeguards: Manually verify that the production environment cannot run with authentication disabled. This might involve:
    • Adding explicit configuration validation that disallows disabling auth in production.
    • Documenting the security impact clearly in the configuration and deployment guides.
    • Integrating tests to enforce that the auth server remains enabled in production settings.

Please review the configuration validations and test suites to confirm that proper safeguards are in place.

server/internal/adapter/gql/gqlmodel/convert_project.go (3)

108-113: New ProjectExport type structure looks good.

The structure includes essential project fields (visualizer, name, description, and imageURL), with proper JSON tag naming conventions. The use of *url.URL for ImageURL and marking it as omitempty is appropriate for handling optional URL values.


115-125: Clean implementation of the conversion function.

The function properly handles nil input checking and field mapping from the project entity to the export structure. This function follows the established pattern for entity conversions in the codebase.


127-137: Consider improving error handling and JSON conversion.

The implementation has the same issues as noted in previous reviews.

Apply this diff to improve the implementation:

 func ToProjectExportFromJSON(data map[string]any) *ProjectExport {
 	var p ProjectExport
-	bytes, err := json.MarshalIndent(data, "", "  ")
+	bytes, err := json.Marshal(data)
 	if err != nil {
-		return nil
+		return nil // Consider logging the error or returning it
 	}
 	if err := json.Unmarshal(bytes, &p); err != nil {
-		return nil
+		return nil // Consider logging the error or returning it
 	}
 	return &p
 }
server/internal/infrastructure/mongo/asset.go (2)

67-81: Good implementation of project filtering in asset retrieval.

The function has been renamed from FindByWorkspace to FindByWorkspaceProject to better reflect its expanded functionality. The implementation correctly handles both cases - filtering by project ID when provided or falling back to team-based filtering. The code structure is clean and follows the appropriate patterns for MongoDB queries.


96-100: Handle type assertion failure case.

The type assertion could silently fail if unsuccessful.

Apply this diff to handle the failure case:

-	if andFilter, ok := mongox.And(filter, "url", bson.M{
-		"$regex": primitive.Regex{Pattern: bucketPattern, Options: "i"},
-	}).(bson.M); ok {
-		filter = andFilter
-	}
+	if andResult := mongox.And(filter, "url", bson.M{
+		"$regex": primitive.Regex{Pattern: bucketPattern, Options: "i"},
+	}); andResult != nil {
+		if andFilter, ok := andResult.(bson.M); ok {
+			filter = andFilter
+		}
+	}
server/internal/adapter/gql/resolver_mutation_asset.go (2)

19-26: Good implementation of optional project ID handling.

The code correctly handles the optional project ID parameter, performs proper type conversion, and provides appropriate error handling. The structure follows established patterns for GraphQL resolvers in the codebase.


41-62: Well-structured implementation of the UpdateAsset mutation.

The new mutation handler is well-structured and follows the established patterns for mutation resolvers. It properly handles parameter validation, ID conversion, and includes appropriate error handling. The response payload properly returns both the asset ID and optional project ID.

server/internal/usecase/interactor/scene.go (4)

668-671: Fixed potential nil pointer dereference.

The code now safely handles the case where the story list could be nil or empty, preventing a potential nil pointer dereference that would occur in the previous implementation. This is a good defensive programming practice.


699-702: Simplified asset handling with direct AddZipAsset calls.

The code now directly uses AddZipAsset instead of a helper function, making the code more straightforward. The error handling is appropriate, logging failures without interrupting the overall export process.


728-732: Type assertion and error handling look good.

The code properly checks if the field value is a URL type before attempting to use it and handles any errors that might occur during asset addition by logging them without interrupting the export process.


753-756: Consistent implementation of asset handling.

This section maintains consistency with the earlier patterns for asset handling, properly checking the type and handling any errors during asset addition.

server/internal/adapter/gql/resolver_mutation_project.go (8)

130-130: Improved zip filename using direct project ID.

The code now uses the project ID directly for naming the zip file, which provides better traceability and debugging.


189-195: Improved asset filename handling with URL parsing.

The new URL parsing approach is more robust for extracting filenames from URLs.


197-200: Structured approach for building export data.

Using conversion functions to format project, plugins, and schemas data improves code clarity and maintainability.


214-214: Handle potential error during workspace ID conversion.

workspace, _ := accountdomain.WorkspaceIDFrom(string(input.TeamID)) discards the error. If TeamID is invalid, subsequent workspace-based operations may fail unexpectedly.

- workspace, _ := accountdomain.WorkspaceIDFrom(string(input.TeamID))
+ workspace, err := accountdomain.WorkspaceIDFrom(string(input.TeamID))
+ if err != nil {
+     return nil, errors.New("invalid team ID")
+ }

224-224: Check potential JSON unmarshal errors.

projectData, _ := unmarshalProject(tempData) discards errors. Consider handling them to avoid nil references if unmarshalling fails.


229-234: Ensure proper transaction handling.

The transaction handling pattern is well implemented with proper deferred cleanup.


257-265: Good approach for updating project image.

The code correctly updates the project's image URL when a matching asset is found in the import data.


359-373: Standardize error handling in unmarshal functions.

The unmarshal helper functions have inconsistent error handling. Type assertions should be checked consistently across all functions.

Example for unmarshalAssets:

 func unmarshalAssets(data []byte) (map[string]string, error) {
     var jsonData map[string]interface{}
     if err := json.Unmarshal(data, &jsonData); err != nil {
         return nil, err
     }

-    assets, _ := jsonData["assets"].(map[string]interface{})
+    assets, ok := jsonData["assets"].(map[string]interface{})
+    if !ok {
+        return nil, errors.New("invalid assets data format")
+    }
     assetNames := make(map[string]string)
     for k, v := range assets {
-        strValue, _ := v.(string)
+        strValue, ok := v.(string)
+        if !ok {
+            return nil, fmt.Errorf("invalid asset name format for key %s", k)
+        }
         assetNames[k] = strValue
     }

     return assetNames, nil
 }
server/e2e/gql_asset_test.go (7)

15-17: Improved test function naming.

Renaming from TestGetAssets to TestCoreSupportGetAssets better indicates the test's specific purpose.


22-62: Enhanced test assertions using JSONEqRegexpValue.

The test now uses a more structured approach with JSONEqRegexpValue for validating asset data, which provides better readability and error messages.


106-113: Updated repository call to support project filtering.

The test correctly uses the new FindByWorkspaceProject method with a nil project ID parameter.


124-179: Comprehensive test for asset-project associations.

This new test function thoroughly validates the full lifecycle of asset-project associations:

  1. Creating projects and associating assets
  2. Querying assets by project
  3. Moving assets between projects
  4. Removing project associations
  5. Asset deletion

Great test coverage!


224-240: Add error handling for updateAsset response.

The function should check for errors in the GraphQL response.

Apply this diff to add error handling:

 func updateAsset(e *httpexpect.Expect, assetId string, projectId *string) *httpexpect.Value {
     requestBody := GraphQLRequest{
         OperationName: "UpdateAsset",
         Query: `mutation UpdateAsset($assetId: ID!, $projectId: ID) {
             updateAsset(input: { assetId: $assetId projectId: $projectId }) {
                 assetId
                 projectId
+                errors {
+                    message
+                    path
+                }
                 __typename
             }
         }`,
         Variables: map[string]any{
             "assetId":   assetId,
             "projectId": projectId,
         },
     }
     return Request(e, uID.String(), requestBody)
 }

242-256: Add error handling for removeAsset response.

Similar to updateAsset, this function should also check for errors.

Apply this diff:

 func removeAsset(e *httpexpect.Expect, assetId string) *httpexpect.Value {
     requestBody := GraphQLRequest{
         OperationName: "RemoveAsset",
         Query: `mutation RemoveAsset($assetId: ID!) {
             removeAsset(input: { assetId: $assetId }) {
                 assetId
+                errors {
+                    message
+                    path
+                }
                 __typename
             }
         }`,
         Variables: map[string]any{
             "assetId": assetId,
         },
     }
     return Request(e, uID.String(), requestBody)
 }

287-347: Updated query with project filtering support.

The getAssets function has been enhanced to support project-based filtering, which aligns with the new API capabilities.

server/internal/usecase/interactor/asset.go (5)

39-51: Renamed function to better reflect its capabilities.

The function has been appropriately renamed from FindByWorkspace to FindByWorkspaceProject to reflect its extended functionality for filtering assets by project.


91-91: Added project association during asset creation.

Assets can now be associated with projects during creation, enhancing asset organization capabilities.


108-127: Validate workspace-to-project consistency.

When updating an existing asset's project, the code does not explicitly confirm the new project is in the same workspace or that the operator can write to it. This might allow transferring an asset to a project in a workspace where the user lacks permission.

+ // Validate that the new project belongs to the same workspace and the operator can write to it
+ if pid != nil {
+    proj, err := i.repos.Project.FindByID(ctx, *pid)
+    if err != nil {
+        return aid, pid, err
+    }
+    if proj.Workspace() != asset.Workspace() {
+        return aid, pid, errors.New("project must be in the same workspace as the asset")
+    }
+    if !operator.IsWritableWorkspace(proj.Workspace()) {
+        return aid, pid, interfaces.ErrOperationDenied
+    }
+ }

154-154: Improved function naming and signature.

Renaming from UploadAssetFile to ImportAssetFiles better reflects its purpose, and adding the project parameter enhances asset organization.


181-182: Added project association during asset import.

Assets can now be associated with projects during import, creating a consistent approach across creation methods.

server/internal/usecase/interactor/project.go (4)

513-513: Renamed function for clarity.

Renaming from ExportProject to ExportProjectData better reflects the function's purpose.


526-526: Refactored asset handling for better encapsulation.

Asset handling has been moved to a dedicated helper function, improving code organization and reusability.


566-566: Updated function signature for better type safety.

The function now takes a strongly-typed workspace parameter instead of a string, improving type safety.


579-588: Improved project creation from imported data.

The code now uses the existing Create method to create a project from imported data, ensuring consistency in project creation across the codebase.

server/e2e/gql_project_export_import_test.go (2)

59-62: Good cleanup of test resources.

The deferred removal of the exported file ensures that tests clean up after themselves, which is a good practice to prevent test file accumulation.


143-173: Good use of defer for file closing.

The function properly ensures the file is closed even if an error occurs during file operations.

server/e2e/gql_storytelling_test.go (5)

51-52: Good test coverage for new Google Analytics fields.

The test correctly verifies the new enableGa and trackingId fields added to the Story type.


513-514: Verification of Google Analytics fields in query.

The fields enableGa and trackingId have been correctly added to the GraphQL query, ensuring they can be fetched and tested.


564-565: Good implementation of GA parameters in the mutation.

The GraphQL mutation for updating a story has been properly extended to include the new Google Analytics parameters.


581-582: Test values for Google Analytics parameters.

The test uses clear, purposeful values for enableGa and trackingId, making it obvious that these are test values.


869-904: Simplified assertion in removeBlock function.

The function now uses a cleaner assertion to verify that the block has been removed.

server/internal/adapter/gql/generated.go (59)

117-117: Added ProjectID complexity function.
No issues found.


498-498: Introduced UpdateAsset complexity function.
Looks consistent with the existing pattern.


803-803: Added projectID parameter to Assets query complexity.
Ensure proper authorization checks when filtering by project.


922-922: Added enableGa complexity function for Story.
No issues found.


938-938: Added trackingId complexity function for Story.
No issues found.


987-987: New Assets complexity in Team.
Implementation looks fine.


1025-1028: Defined UpdateAssetPayload in complexity map.
No concerns here.


1214-1214: Declared UpdateAsset in MutationResolver.
Please verify that only authorized users can update assets.


1351-1351: Added optional projectID in Assets query for filtering.
Confirm access control logic.


1405-1405: Added projectID to TeamResolver.Assets.
No issues found.


1515-1520: Included Asset.projectId in complexity selection.
Looks consistent with how other asset fields are implemented.


3544-3555: Integrated Mutation.updateAsset complexity.
This aligns well with the new payload.


5251-5251: Extended Query.Assets to handle assignment of projectID.
Implementation appears correct.


5803-5809: Introduced enableGa field for Story complexity.
Matches the PR objective.


5915-5921: Introduced trackingId field for Story complexity.
Implementation looks straightforward.


6142-6142: Added optional projectId in Team.Assets query complexity.
No issues found.


6310-6323: Added UpdateAssetPayload complexity fields (assetId, projectId).
No issues.


6702-6702: Registered UpdateAssetInput for unmarshalling.
All good here.


6954-6954: projectId added to Asset schema.
Implementation seems correct.


6978-6981: UpdateAssetInput introduced.
No issues noted.


6998-7001: UpdateAssetPayload type declared.
Aligns with the new mutation.


7024-7024: projectId in assets query definition.
No issues found.


7033-7033: updateAsset mutation in Mutation definition.
Implementation consistent with payload changes.


8110-8111: Added enableGa, trackingId (non-null) to Story type.
Make sure the front-end always provides these fields.


8172-8175: Optional enableGa / trackingId in UpdateStoryInput.
Ensures partial updates.


8627-8640: Added assets(projectId: ID...) & projects(...) to Team.
This provides flexible queries.


8740-8742: Changed lines in removeMemberFromTeam.
Unrelated to GA changes; no issues.


10237-10263: Arguments extraction for UpdateAsset mutation.
Implementation matches the new input type.


11113-11132: Handling projectId param inside Query.assets argument parsing.
Implementation looks correct.


11153-11169: field_Query_assets_argsProjectID introduced.
No concerns.


11679-11704: Handling projectId for Team.assets argument parsing.
All good.


12604-12642: _Asset_projectId field resolver.
Implementation consistent with optional project ID logic.


13019-13020: Field context references for _Asset_projectId.
No issues found.


13224-13225: Additional references to _Asset_projectId in field context.
No issues.


13598-13599: Field context references to _Asset_projectId (again).
All consistent.


13954-13957: enableGa, trackingId in Story field context.
No concerns.


14328-14331: enableGa, trackingId field context for Story repeated.
Implementation looks fine.


22555-22558: More occurrences of _Story_enableGa & _Story_trackingId.
No issues found.


22807-22810: Recurring _Story_enableGa & _Story_trackingId references.
All good.


23031-23034: Story fields enableGa, trackingId repeated.
No issues.


23186-23244: _Mutation_updateAsset field & resolver method.
Make sure to validate user permissions in actual resolver logic.


37265-37265: Invoked ec.resolvers.Query().Assets with projectId.
Check authorization in the query resolver.


38863-38866: Referencing _Story_enableGa & _Story_trackingId.
No new concerns.


39801-39804: Repetition of enableGa, trackingId references in Story.
Looks fine.


41920-42006: _Story_enableGa & _Story_trackingId fields with non-null checks.
Implementation aligns with the schema definition.


43209-43212: Additional references to enableGa & trackingId.
No issues found.


43301-43304: Continuation of enableGa & trackingId references.
No problems.


43867-43867: ec.resolvers.Team().Assets call with optional projectId.
Please confirm permission checks in the resolver.


44693-44777: _UpdateAssetPayload_assetId and _UpdateAssetPayload_projectId.
Payload structure is correct.


49478-49478: Reordered fields for CreateAssetInput to include projectId.
No concerns.


49492-49492: Assigned projectId in CreateAssetInput switching logic.
Implementation is consistent.


51273-51305: unmarshalInputUpdateAssetInput function introduced.
No issues identified.


51899-51899: Insert enableGa, trackingId in UpdateStoryInput field order.
Implementation is correct.


52011-52024: Handling optional enableGa/trackingId during story updates.
No issues.


52963-52963: Rendering _Asset_projectId in Asset field selection.
Looks good.


61476-61485: _Story_enableGa & _Story_trackingId in field rendering.
Implementation is correct.


62386-62425: _UpdateAssetPayload marshalling logic.
No concerns.


66293-66296: unmarshalNUpdateAssetInput non-null logic.
No issues found.


67705-67709: marshalOUpdateAssetPayload optional logic.
Implementation is consistent with the schema.

server/e2e/seeder.go (1)

121-163: Add validation for asset file size and content type.

The function should validate file size and content type before uploading to prevent potential security issues.

 func addAsset(path string, ctx context.Context, r *repo.Container, gf gateway.File) (*url.URL, error) {
     f, err := os.Open(path)
     if err != nil {
         return nil, err
     }
     defer func() {
         if cerr := f.Close(); cerr != nil && err == nil {
             err = cerr
         }
     }()
     stat, err := f.Stat()
     if err != nil {
         return nil, err
     }
+    // Validate file size (e.g., max 10MB)
+    if stat.Size() > 10*1024*1024 {
+        return nil, fmt.Errorf("file size %d exceeds maximum allowed size of 10MB", stat.Size())
+    }
+
     contentType := mime.TypeByExtension(filepath.Ext(path))
     if contentType == "" {
         contentType = "application/octet-stream"
     }
+    // Validate content type
+    allowedTypes := map[string]bool{
+        "image/jpeg": true,
+        "image/png":  true,
+        "image/gif":  true,
+    }
+    if !allowedTypes[contentType] {
+        return nil, fmt.Errorf("unsupported content type: %s", contentType)
+    }

Comment on lines 303 to 304
return err
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix potential error handling bug.

The function ends with return err after several operations. If errors occurred earlier but were later successful, this would return the last error value (which could be nil) even though earlier errors occurred.

- return err
+ return nil
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return err
}
return nil
}

Comment on lines 275 to 304
func addStory(ctx context.Context, r *repo.Container) error {
schema := builtin.GetPropertySchema(builtin.PropertySchemaIDStory)
prop, err := property.New().NewID().Schema(schema.ID()).Scene(sID).Build()
if err != nil {
return err
}
pages := []*storytelling.Page{}
page, err := _createPage(ctx, r)
if err != nil {
return err
}
pages = append(pages, page)
story, err := storytelling.NewStory().
ID(storyID).
Title("test page").
Pages(storytelling.NewPageList(pages)).
Scene(sID).
Property(prop.ID()).
Build()
if err != nil {
return err
}
if err = r.Property.Save(ctx, prop); err != err {
return err
}
if err := r.Storytelling.Save(ctx, *story); err != nil {
return err
}
return err
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

❓ Verification inconclusive

Add Google Analytics parameters to Story component.

According to the PR objectives, you need to add enableGa and trackingId parameters to the Story component. These aren't currently implemented in the addStory function.

 story, err := storytelling.NewStory().
   ID(storyID).
   Title("test page").
   Pages(storytelling.NewPageList(pages)).
   Scene(sID).
   Property(prop.ID()).
+  EnableGa(true).
+  TrackingId("UA-12345678-1").
   Build()

🏁 Script executed:

#!/bin/bash
# Check if the Story struct has the enableGa and trackingId fields

# Search for Story struct definition
rg -n "type Story struct" --type go

# Check if the fields exist in the Story struct
rg -A 20 "type Story struct" --type go | grep -E "EnableGa|TrackingId"

# Search for NewStory builder function to see if it supports these fields
rg -n "func NewStory" --type go

Length of output: 1172


Action Required: Update the Google Analytics integration in the Story creation flow

It has been verified that the current implementation of the Story component does not support the Google Analytics parameters—namely, EnableGa and TrackingId. The Story struct (in server/pkg/storytelling/story.go) and its corresponding builder (in server/pkg/storytelling/story_bulider.go) do not appear to expose or set these fields, which means that simply adding the builder method calls in the addStory function won’t have the intended effect.

Please address the following:

  • In server/e2e/seeder.go (lines 275–304): Update the builder chain in the addStory function to include the new parameters:
     story, err := storytelling.NewStory().
       ID(storyID).
       Title("test page").
       Pages(storytelling.NewPageList(pages)).
       Scene(sID).
       Property(prop.ID()).
    +  EnableGa(true).
    +  TrackingId("UA-12345678-1").
       Build()
  • In the Story implementation (likely in server/pkg/storytelling/story.go and server/pkg/storytelling/story_bulider.go):
    Ensure that both the Story struct and its builder are updated to define and correctly process the EnableGa and TrackingId fields. This may involve:
    • Adding the new fields to the Story struct.
    • Implementing the corresponding builder methods that set these fields in the final Story object.

Once these changes are made, please verify that the Story component correctly handles the analytics parameters during story creation.

[tag: fix_required]

Committable suggestion skipped: line range outside the PR's diff.

@hexaforce hexaforce force-pushed the feat/storytelling-ga-support branch 2 times, most recently from 3264116 to 07ea4c4 Compare February 26, 2025 05:32
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
server/internal/usecase/interactor/asset.go (1)

39-49: 🛠️ Refactor suggestion

Validate project-workspace consistency for read operations
Verify that the specified project, if present, belongs to the given workspace and that the user can read it.

♻️ Duplicate comments (2)
server/e2e/gql_asset_test.go (2)

224-240: 🛠️ Refactor suggestion

Add error handling for updateAsset response.

The function should check for errors in the GraphQL response.

Apply this diff to add error handling:

 func updateAsset(e *httpexpect.Expect, assetId string, projectId *string) *httpexpect.Value {
     requestBody := GraphQLRequest{
         OperationName: "UpdateAsset",
         Query: `mutation UpdateAsset($assetId: ID!, $projectId: ID) {
             updateAsset(input: { assetId: $assetId projectId: $projectId }) {
                 assetId
                 projectId
+                errors {
+                    message
+                    path
+                }
                 __typename
             }
         }`,
         Variables: map[string]any{
             "assetId":   assetId,
             "projectId": projectId,
         },
     }
     return Request(e, uID.String(), requestBody)
 }

242-256: 🛠️ Refactor suggestion

Add error handling for removeAsset response.

Similar to updateAsset, this function should also check for errors.

Apply this diff:

 func removeAsset(e *httpexpect.Expect, assetId string) *httpexpect.Value {
     requestBody := GraphQLRequest{
         OperationName: "RemoveAsset",
         Query: `mutation RemoveAsset($assetId: ID!) {
             removeAsset(input: { assetId: $assetId }) {
                 assetId
+                errors {
+                    message
+                    path
+                }
                 __typename
             }
         }`,
         Variables: map[string]any{
             "assetId": assetId,
         },
     }
     return Request(e, uID.String(), requestBody)
 }
🧹 Nitpick comments (13)
server/pkg/nlslayer/feature_test.go (2)

13-14: Consider renaming the test function to match the new implementation.

The test function is still named TestNewFeatureWithNewId but it's now testing the NewFeature function with a generated ID. Renaming it to something like TestNewFeatureWithGeneratedId would better reflect what's being tested and avoid confusion for future developers.

-func TestNewFeatureWithNewId(t *testing.T) {
+func TestNewFeatureWithGeneratedId(t *testing.T) {

9-24: Consider consolidating duplicate test code.

There's significant duplication between TestNewFeatureWithNewId and TestNewFeature. Both tests are very similar with the only real difference being how the ID is created. Consider creating a helper function or using table-driven tests to reduce duplication and improve maintainability.

func TestNewFeature(t *testing.T) {
    testCases := []struct {
        name       string
        idProvider func() FeatureID
    }{
        {
            name:       "with generated ID",
            idProvider: func() FeatureID { return NewFeatureID() },
        },
        {
            name:       "with explicit ID",
            idProvider: func() FeatureID { return NewFeatureID() }, // Same for test, but makes intent clear
        },
    }

    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            id := tc.idProvider()
            featureType := "Feature"
            point := NewPoint("Point", []float64{1, 2})
            properties := map[string]any{"key1": "value1"}
            
            f, err := NewFeature(
                id,
                featureType,
                point,
            )
            f.UpdateProperties(&properties)

            assert.NoError(t, err)
            assert.Equal(t, id, f.ID())
            assert.Equal(t, featureType, f.FeatureType())
            assert.Equal(t, point, f.Geometry())
            assert.Equal(t, properties, *f.Properties())
        })
    }
}
web/src/services/i18n/translations/en.yml (1)

130-130: Ensure Default Text for "More" Key
The "More:" key is newly added with an empty default value. Please verify if an empty string is intended or if a placeholder/default text should be provided for clarity in the UI.

web/src/beta/features/Navbar/LeftSection/index.tsx (1)

75-80: Consistent tooltip implementation with minor indentation issue.

The tooltipText implementation is good, but there's an indentation inconsistency on line 79 (two extra spaces).

-            tooltipText={t("Editor")}
+          tooltipText={t("Editor")}
web/src/beta/features/PluginPlayground/Plugins/presets/viewerAndSceneSettings/mouseEvent.ts (1)

20-90: Interactive coordinate display implementation

The widget implementation provides a clean UI for displaying coordinates with good styling and structure. The communication between the viewer and UI is properly handled with event listeners and message passing.

A few minor suggestions to enhance the code:

-      <div>Latitude: <span id="lat" class="coordinate-value">-</span>°</div>
-      <div>Longitude: <span id="lng" class="coordinate-value">-</span>°</div>
-      <div>Height: <span id="height" class="coordinate-value">-</span> m</div>
+      <div>Latitude: <span id="lat" class="coordinate-value">-</span><span aria-hidden="true">°</span></div>
+      <div>Longitude: <span id="lng" class="coordinate-value">-</span><span aria-hidden="true">°</span></div>
+      <div>Height: <span id="height" class="coordinate-value">-</span><span aria-hidden="true"> m</span></div>
  1. Consider separating the units from the values with aria-hidden to improve screen reader experience
  2. Consider adding aria-live="polite" to the coordinate container to announce changes to screen readers
server/internal/usecase/interactor/scene.go (1)

699-701: Consider handling url.Parse errors.
Currently, the error from url.Parse(urlStr) is ignored, which could lead to passing an invalid path to AddZipAsset.

server/internal/usecase/interfaces/asset.go (1)

41-41: Add documentation for the Update method.
Consider adding a comment explaining what the method does with the optional ProjectID.

server/e2e/gql_project_export_import_test.go (1)

122-141: Consider adding error checks for an empty response in exporProject.
If the response is empty, the file might be invalid. A simple check helps detect issues early.

server/internal/adapter/gql/resolver_mutation_project.go (1)

178-182: Remove commented-out code
Please remove the commented-out lines for clarity since they are no longer used.

- // pid2, err := gqlmodel.ToID[id.Project](gqlmodel.ID(prj.ID().String()))
- // if err != nil {
- //   return nil, err
- // }
server/e2e/gql_storytelling_test.go (2)

15-55: Well-structured end-to-end coverage for basic Story CRUD.
This block of code thoroughly tests creating, fetching, updating, and deleting stories, including verifying new fields enableGa and trackingId. As a minor enhancement, consider adding an assertion to confirm that the updated story title ("test2") is reflected in the fetched result.


231-413: Thorough story publishing test.
The generated JSON is verified in-depth (including default GA fields), ensuring your published story matches expectations. The test is somewhat extensive; if it grows further, consider splitting parts of it into separate tests for improved readability.

server/e2e/seeder.go (2)

265-265: Clarify the index parameter usage.
Using -1 as a position index for widget insertion might be confusing to new contributors. Consider documenting that negative indices imply an append operation.


287-293: Consider setting default GA parameters during the seeding process.
If you want seeded stories to include GA settings by default, add calls to .EnableGa(...) and .TrackingId(...) when building the story. Otherwise, they remain uninitialized in this seed data.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d65fac8 and 07ea4c4.

⛔ Files ignored due to path filters (1)
  • server/e2e/test.zip is excluded by !**/*.zip
📒 Files selected for processing (81)
  • server/e2e/common.go (7 hunks)
  • server/e2e/gql_asset_test.go (6 hunks)
  • server/e2e/gql_me_test.go (1 hunks)
  • server/e2e/gql_project_export_import_test.go (3 hunks)
  • server/e2e/gql_project_export_test.go (0 hunks)
  • server/e2e/gql_project_test.go (6 hunks)
  • server/e2e/gql_scene_test.go (4 hunks)
  • server/e2e/gql_storytelling_test.go (6 hunks)
  • server/e2e/gql_user_test.go (2 hunks)
  • server/e2e/gql_validate_geojson_test.go (1 hunks)
  • server/e2e/mock_test.go (1 hunks)
  • server/e2e/ping_test.go (1 hunks)
  • server/e2e/seeder.go (5 hunks)
  • server/go.mod (1 hunks)
  • server/gql/asset.graphql (5 hunks)
  • server/gql/storytelling.graphql (3 hunks)
  • server/gql/workspace.graphql (2 hunks)
  • server/gqlgen.yml (0 hunks)
  • server/internal/adapter/gql/generated.go (61 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_asset.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go (5 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_project.go (2 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (5 hunks)
  • server/internal/adapter/gql/loader_asset.go (2 hunks)
  • server/internal/adapter/gql/resolver_mutation_asset.go (3 hunks)
  • server/internal/adapter/gql/resolver_mutation_project.go (6 hunks)
  • server/internal/adapter/gql/resolver_mutation_storytelling.go (1 hunks)
  • server/internal/adapter/gql/resolver_query.go (1 hunks)
  • server/internal/adapter/gql/resolver_team.go (1 hunks)
  • server/internal/app/app.go (2 hunks)
  • server/internal/app/auth_client.go (1 hunks)
  • server/internal/infrastructure/fs/file.go (2 hunks)
  • server/internal/infrastructure/fs/file_test.go (3 hunks)
  • server/internal/infrastructure/memory/asset.go (1 hunks)
  • server/internal/infrastructure/mongo/asset.go (2 hunks)
  • server/internal/infrastructure/mongo/mongodoc/asset.go (3 hunks)
  • server/internal/infrastructure/mongo/mongodoc/scene.go (0 hunks)
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go (4 hunks)
  • server/internal/usecase/interactor/asset.go (6 hunks)
  • server/internal/usecase/interactor/nlslayer.go (3 hunks)
  • server/internal/usecase/interactor/project.go (5 hunks)
  • server/internal/usecase/interactor/project_test.go (0 hunks)
  • server/internal/usecase/interactor/scene.go (5 hunks)
  • server/internal/usecase/interactor/storytelling.go (3 hunks)
  • server/internal/usecase/interactor/storytelling_test.go (1 hunks)
  • server/internal/usecase/interfaces/asset.go (2 hunks)
  • server/internal/usecase/interfaces/project.go (2 hunks)
  • server/internal/usecase/interfaces/story.go (1 hunks)
  • server/internal/usecase/repo/asset.go (1 hunks)
  • server/pkg/asset/asset.go (2 hunks)
  • server/pkg/asset/builder.go (1 hunks)
  • server/pkg/asset/id.go (1 hunks)
  • server/pkg/nlslayer/feature.go (0 hunks)
  • server/pkg/nlslayer/feature_test.go (1 hunks)
  • server/pkg/scene/builder/story.go (1 hunks)
  • server/pkg/storytelling/story.go (4 hunks)
  • server/pkg/storytelling/story_bulider.go (1 hunks)
  • web/src/beta/features/Dashboard/ContentsContainer/Projects/Project/hooks.tsx (1 hunks)
  • web/src/beta/features/Dashboard/ContentsContainer/Projects/index.tsx (1 hunks)
  • web/src/beta/features/Dashboard/index.tsx (1 hunks)
  • web/src/beta/features/Editor/Map/InspectorPanel/LayerInspector/DataSource/SketchCustomProperties/CustomPropertyFieldItem.tsx (1 hunks)
  • web/src/beta/features/Editor/Map/InspectorPanel/LayerInspector/index.tsx (6 hunks)
  • web/src/beta/features/Editor/Map/LayerStylePanel/Editor/StyleInterface/index.tsx (3 hunks)
  • web/src/beta/features/Editor/Map/LayerStylePanel/PresetLayerStyle/index.tsx (3 hunks)
  • web/src/beta/features/Editor/Map/LayerStylePanel/index.tsx (1 hunks)
  • web/src/beta/features/Editor/Map/LayersPanel/LayerItem.tsx (3 hunks)
  • web/src/beta/features/Editor/Map/ToolsPanel/index.tsx (3 hunks)
  • web/src/beta/features/Navbar/LeftSection/index.tsx (1 hunks)
  • web/src/beta/features/PluginPlayground/Plugins/presets/index.ts (2 hunks)
  • web/src/beta/features/PluginPlayground/Plugins/presets/viewerAndSceneSettings/mouseEvent.ts (1 hunks)
  • web/src/beta/lib/reearth-ui/components/Icon/index.tsx (3 hunks)
  • web/src/beta/lib/reearth-ui/components/IconButton/index.tsx (4 hunks)
  • web/src/beta/lib/reearth-ui/components/Popup/hooks.ts (4 hunks)
  • web/src/beta/lib/reearth-ui/components/Popup/index.tsx (5 hunks)
  • web/src/beta/lib/reearth-ui/components/Tabs/index.tsx (4 hunks)
  • web/src/beta/lib/reearth-ui/components/Tooltip/index.tsx (3 hunks)
  • web/src/beta/ui/components/EntryItem/index.tsx (3 hunks)
  • web/src/beta/ui/components/ManagerBase/ManagerHeader.tsx (1 hunks)
  • web/src/services/i18n/translations/en.yml (6 hunks)
  • web/src/services/i18n/translations/ja.yml (6 hunks)
💤 Files with no reviewable changes (5)
  • server/gqlgen.yml
  • server/internal/infrastructure/mongo/mongodoc/scene.go
  • server/pkg/nlslayer/feature.go
  • server/internal/usecase/interactor/project_test.go
  • server/e2e/gql_project_export_test.go
✅ Files skipped from review due to trivial changes (3)
  • web/src/beta/features/Dashboard/ContentsContainer/Projects/Project/hooks.tsx
  • web/src/beta/features/Dashboard/index.tsx
  • web/src/beta/features/Dashboard/ContentsContainer/Projects/index.tsx
🚧 Files skipped from review as they are similar to previous changes (14)
  • server/e2e/mock_test.go
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go
  • server/e2e/gql_me_test.go
  • server/pkg/storytelling/story_bulider.go
  • server/internal/usecase/interfaces/story.go
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go
  • server/internal/usecase/interactor/storytelling_test.go
  • server/internal/adapter/gql/resolver_mutation_storytelling.go
  • server/e2e/gql_project_test.go
  • server/pkg/storytelling/story.go
  • server/gql/storytelling.graphql
  • server/internal/usecase/interactor/storytelling.go
  • server/internal/adapter/gql/gqlmodel/models_gen.go
  • server/e2e/common.go
🧰 Additional context used
🧠 Learnings (2)
server/internal/adapter/gql/resolver_mutation_project.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:0-0
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project export functionality, the ZIP file is deleted after saving to storage.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1151
File: server/internal/adapter/gql/resolver_mutation_project.go:215-217
Timestamp: 2024-11-12T15:21:04.150Z
Learning: In the project import functionality, the file names are unique and can be safely replaced uniformly using `bytes.Replace`.
server/e2e/gql_project_export_import_test.go (2)
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:48-80
Timestamp: 2024-11-12T15:21:04.151Z
Learning: In `server/e2e/gql_import_export_test.go`, test functions like `TestCallImportProject` may have disabled assertions due to problems with GraphQL.
Learnt from: hexaforce
PR: reearth/reearth-visualizer#1141
File: server/e2e/gql_import_export_test.go:12-44
Timestamp: 2024-11-12T15:21:04.150Z
Learning: The response body assertions in `TestCallExportProject` are disabled due to existing problems with GraphQL.
🔇 Additional comments (206)
server/pkg/scene/builder/story.go (1)

45-45: Fixed missing Title field in storyJSON serialization.

The change correctly adds the Title field to the storyJSON object returned by the method. This ensures the story title is properly included in the JSON output, which is consistent with how other title fields are handled in the codebase (like in pageJSON).

web/src/beta/lib/reearth-ui/components/Popup/hooks.ts (6)

11-12: Enhanced Popup with arrow functionality.

The import of the arrow function from @floating-ui/react is appropriate for adding visual arrow indicators to the popup.


14-14: Proper import of useRef for the arrow element.

The addition of useRef import is necessary for creating a reference to the arrow DOM element.


29-29: Arrow reference properly initialized.

The arrowRef is correctly initialized as null and will be populated when the component renders.


55-56: Arrow middleware correctly implemented.

The arrow middleware is properly added to the floating UI configuration, which will handle the positioning of the arrow element relative to the popup and its anchor.


75-75: Arrow reference properly exposed in the hook's return value.

Exposing the arrowRef in the returned object makes it available to the component using this hook, allowing it to properly connect the DOM element to the ref.


79-79: Dependencies array is correctly maintained.

The arrowRef is intentionally omitted from the useMemo dependencies array since the ref object itself is stable (only its .current property changes), which is the correct implementation.

web/src/services/i18n/translations/en.yml (6)

151-151: Verify "Layer Style" Key Usage
The "Layer Style:" key is introduced with an empty value. Confirm that this entry is consistent with similar UI elements across the application and that a meaningful default (or translation note) is provided where needed.


161-162: Check "Fly to" and "Visibility" Keys
The new keys "Fly to:" and "Visibility:" are added with empty strings. Please ensure that their default values will be set at runtime or later updated by translators to meet UI expectations.


169-173: Review "Style" Keys for Map Elements
A set of style-related keys ("Marker style:", "Polyline style:", "Polygon style:", "3dtiles style:", and "3D model style:") have been added with empty default values. Verify that these keys are either populated dynamically or will receive proper default translations to maintain UI consistency.


183-184: Confirm "Assign style" and "New style" Keys
The keys "Assign style:" and "New style:" have been introduced with empty placeholders. Ensure these entries integrate seamlessly with the rest of the style management UI and consider providing default text if appropriate.


259-260: New UI Module Keys Added
The additions of "Dashboard:" and "Editor:" are noticeable. Although their default values are empty, please verify that the UI or localization process supplies the proper texts for these modules to avoid display issues.


392-393: Evaluate Layout Keys
The new "Grid Layout:" and "List Layout:" keys are added with empty values. Confirm that the intended default strings will be provided elsewhere or update these entries to meet UI feedback for layout selections.

web/src/services/i18n/translations/ja.yml (7)

133-133: "More" Key Translation Confirmation
The "More:" key now has the Japanese translation "その他". Please verify that this translation fits the context in which it's used and is consistent with the overall tone of the interface.


152-155: Review Translation Consistency for Layer Details
The entries for "Layer:", "Feature:", "Layer Style:" (translated as "レイヤースタイル"), and "Infobox:" (translated as "インフォボックス") have been localized. Ensure these translations are contextually appropriate and consistent with similar terms elsewhere in the application.


164-165: Verify "Fly to" and "Visibility" Translations
The new keys "Fly to:" and "Visibility:" are localized as "移動" and "表示・非表示切り替え" respectively. Please double-check that these translations accurately reflect the intended UI actions and terminology.


172-176: Check Translated Style Keys
The style keys "Marker style:", "Polyline style:", "Polygon style:", "3dtiles style:", and "3D model style:" have been translated as "マーカースタイル", "ポリラインスタイル", "ポリゴンスタイル", "3Dタイルスタイル", and "3Dモデルのスタイル". Verify that these translations align with the product’s design terminology and are consistently used in related components.


186-187: Confirm "Assign style" and "New style" Translations
The keys "Assign style:" and "New style:" are now rendered as "スタイルの割り当て" and "新しいスタイル" respectively. Please check for consistency in language tone and ensure they meet the overall style guidelines of the application.


262-263: Dashboard and Editor Key Translations
The newly added "Dashboard:" and "Editor:" entries are translated as "ダッシュボード" and "エディタ". Confirm that these translations are accurate and consistently applied throughout the UI.


395-396: Validate Layout Key Translations
The "Grid Layout:" and "List Layout:" keys are localized as "グリッドレイアウト" and "リストのレイアウト". Ensure that these translations effectively convey the intended layout options and are in line with other parts of the interface.

server/internal/adapter/gql/gqlmodel/convert_nlslayer_test.go (5)

11-13: Method signature update aligned with package changes

The change from implicit to explicit feature ID generation is consistent with the removal of NewFeatureWithNewId and reflects the new pattern of creating features with explicitly generated IDs.


21-23: Consistent implementation of new feature creation pattern

The explicit ID generation approach is consistently applied across all feature creation calls.


31-33: Consistent implementation of new feature creation pattern

The explicit ID generation approach is consistently applied for polygon features as well.


41-43: Consistent implementation of new feature creation pattern

The explicit ID generation approach is consistently applied for multi-polygon features.


51-53: Consistent implementation of new feature creation pattern

The explicit ID generation approach is consistently applied for geometry collection features.

server/internal/usecase/interactor/nlslayer.go (3)

893-895: Method signature update aligned with package changes

The change from implicit to explicit feature ID generation in AddGeoJSONFeature follows the new pattern implemented across the codebase, enhancing clarity around ID generation.


1154-1159: Method signature update aligned with package changes

The change in ImportNLSLayers ensures consistent implementation of the new feature creation pattern with explicit ID generation.


1162-1164:

✅ Verification successful

Verify feature property handling

The feature properties handling following the new creation pattern is properly implemented, but should be verified with existing clients of this functionality.

Run the following script to check if any code relies on the old feature creation pattern:


🏁 Script executed:

#!/bin/bash
# Search for any remaining instances of NewFeatureWithNewId
rg "NewFeatureWithNewId" --type go

Length of output: 115


Verified: New Feature Property Handling is Confirmed

The new pattern for handling feature properties is correctly implemented. The search for NewFeatureWithNewId only returned a reference in the test file (server/pkg/nlslayer/feature_test.go), which suggests that no production code is relying on the old creation pattern. Please ensure that the tests accurately reflect the intended behavior, but no further modifications seem necessary.

web/src/beta/features/Editor/Map/LayerStylePanel/index.tsx (1)

75-76: Good enhancement with tooltip!

Adding a tooltip to the "Assign style" button improves user experience by making the button's function more clear.

web/src/beta/ui/components/ManagerBase/ManagerHeader.tsx (2)

189-189: Great UX improvement with tooltip!

Adding the tooltipText property for the grid layout button enhances user experience by clarifying the button's purpose.


196-196: Consistent tooltip implementation!

Adding tooltipText for the list layout button maintains UI consistency and improves usability.

web/src/beta/features/Editor/Map/InspectorPanel/LayerInspector/DataSource/SketchCustomProperties/CustomPropertyFieldItem.tsx (1)

62-62: Good UX enhancement with tooltip!

Adding tooltipText to the options button improves discoverability and clarifies the button's purpose.

web/src/beta/features/Navbar/LeftSection/index.tsx (1)

66-71: Great navigation enhancement!

Adding tooltipText to the Dashboard button improves usability by making the navigation purpose clear.

web/src/beta/ui/components/EntryItem/index.tsx (3)

8-8: Internationalization support added

Good addition of the i18n service import to support localization of UI elements.


46-46: Added translation support

Properly implemented the useT hook to enable internationalization support within the component.


101-102: Enhanced user experience with tooltip

Great addition of tooltip text and placement for the "More" button, which improves accessibility and user experience by providing context for the action.

web/src/beta/features/PluginPlayground/Plugins/presets/index.ts (2)

31-36: Added new UI component imports

Good organization of imports, keeping related UI components together and adding the new mouseEvents import in the appropriate section with other viewer settings.


67-73: Added mouseEvents plugin to viewer settings

Well-structured addition of the mouseEvents plugin to the presetPlugins array, maintaining consistent formatting with the existing code by using a multi-line array.

web/src/beta/features/PluginPlayground/Plugins/presets/viewerAndSceneSettings/mouseEvent.ts (3)

1-18: Well-structured plugin configuration file

The plugin metadata is well-organized with appropriate ID, name, and version information. The extension configuration correctly specifies the widget type.


78-88: Proper event handling in viewer

The click event handler correctly captures and extracts coordinates, and the message structure is clear and consistent.


92-96: Clean plugin export

The plugin export is well-structured with a clear ID, title, and files array.

web/src/beta/features/Editor/Map/LayersPanel/LayerItem.tsx (3)

95-96: Enhanced accessibility with tooltips

Good addition of tooltip text for the "Fly to" action, improving user experience by providing context for the button's function.


108-109: Improved UI with visibility tooltip

Well-implemented tooltip for the visibility toggle button, maintaining consistency with other tooltips in the application.


117-124: Updated dependency array for useMemo

Properly updated the dependency array to include the newly added dependencies (editingLayerNameId and t), ensuring the memoized value is recalculated when these values change.

web/src/beta/features/Editor/Map/LayerStylePanel/Editor/StyleInterface/index.tsx (4)

3-3: Good internationalization implementation.

The useT hook is correctly imported to support localization of UI elements.


34-34: LGTM!

Properly initializing the translation function from the useT hook.


108-117: Well-structured translation mapping for appearance types.

Good implementation of a memoized object that maps each appearance type to its translated label. Using useMemo with the translation function as a dependency ensures the mapping is updated when the language changes.


119-140: Enhanced tabs with proper tooltips and internationalization.

The appearance type tabs now correctly include tooltips with translated text, improving the user experience for non-English speakers. The structure with tooltip placement is consistent with best practices.

web/src/beta/features/Editor/Map/LayerStylePanel/PresetLayerStyle/index.tsx (3)

7-7: LGTM for i18n import.

The useT hook is correctly imported to support localization of UI elements.


38-38: LGTM!

Properly initializing the translation function.


152-159: Enhanced button with accessible tooltip.

Good implementation of tooltips for the "New style" button, improving user experience and accessibility through proper internationalization.

web/src/beta/lib/reearth-ui/components/IconButton/index.tsx (4)

4-4: LGTM!

Updated import to include IconProps type which is necessary for the tooltip functionality.


18-18: Good type extension for tooltip support.

Properly extending IconButtonProps with tooltip-related properties from IconProps using Pick.


30-33: LGTM for prop destructuring.

Added the new tooltip-related props to the component's destructuring pattern.


55-61: Well-implemented tooltip integration.

The Icon component now correctly receives the tooltip-related props with a consistent offset, enabling proper tooltip display across the application.

web/src/beta/features/Editor/Map/InspectorPanel/LayerInspector/index.tsx (4)

10-10: LGTM for i18n import.

The useT hook is correctly imported to support localization.


50-50: LGTM!

Properly initializing the translation function.


91-93: Good tooltip implementation with consistent placement.

Each tab now has properly translated tooltips with consistent left placement, improving the user experience and accessibility for non-English users.

Also applies to: 100-102, 122-124, 137-139


147-162: Updated dependency array with translation function.

The dependencies array now correctly includes the translation function and has been reorganized for better readability and maintenance.

web/src/beta/lib/reearth-ui/components/Tooltip/index.tsx (7)

16-18: Good addition of customization props to improve tooltip flexibility.

The addition of offset, iconColor, and placement props provides better customization options for the Tooltip component, making it more versatile and reusable across different contexts.


21-28: Well-structured prop destructuring with default values.

Good implementation of prop destructuring with a sensible default for the offset (6). This makes the component more user-friendly by providing reasonable defaults while allowing for customization.


49-51: Props are correctly passed to the Popup component.

The new offset and placement props are properly forwarded to the Popup component, ensuring proper positioning of tooltips.


54-59: Good conditional styling based on tooltip type.

The icon color is now properly conditional based on whether it's an experimental tooltip (using theme.warning.weakest) or a custom tooltip (using the provided iconColor). This improves visual consistency.


69-76: Consistent conditional styling for Typography component.

The Typography color is appropriately conditioned on the tooltip type, matching the icon styling behavior and providing consistent visual feedback.


1-1:

✅ Verification successful

Ensure correct import is used for Placement type.

The code imports Placement type from @floating-ui/react. This is necessary for the new placement prop, but ensure this is the correct package and interface according to your project dependencies.


🏁 Script executed:

#!/bin/bash
# Verify if @floating-ui/react is a dependency and how it's used elsewhere
grep -r "@floating-ui/react" --include="package.json" .
grep -r "import.*Placement.*from \"@floating-ui/react\"" --include="*.tsx" --include="*.ts" .

Length of output: 517


Verified: Correct import for Placement

The search results confirm that @floating-ui/react is correctly listed as a dependency (version 0.24.7) and used consistently in several files including Tooltip/index.tsx. No changes are needed here.


66-67:

✅ Verification successful

Added 'tooltip' prop to Popup component.

Ensure this property is defined in the Popup component to properly handle tooltip-specific styling.


🏁 Script executed:

#!/bin/bash
# Check if Popup component has the tooltip prop
grep -A 15 "export type PopupProps" web/src/beta/lib/reearth-ui/components/Popup/index.tsx

Length of output: 482


Popup tooltip prop verified
The tooltip property is correctly defined in the PopupProps type in web/src/beta/lib/reearth-ui/components/Popup/index.tsx, so no further modifications are needed.

web/src/beta/lib/reearth-ui/components/Icon/index.tsx (5)

1-2: Appropriate imports for new functionality.

The import of Placement from @floating-ui/react and Tooltip from the project's UI library are necessary for the new tooltip functionality being added to the Icon component.


16-18: Good addition of tooltip-related properties to IconProps.

The addition of tooltipText, offset, and placement properties to the IconProps type allows for enhanced tooltip functionality, making the Icon component more versatile.


27-29: Props properly destructured for use.

The new props are correctly destructured in the component function signature, maintaining consistency with the existing code style.


48-56: Good refactoring to extract Icon rendering logic.

The refactoring to extract the Icon rendering into an IconComponent variable improves code readability and makes the conditional rendering based on tooltip presence cleaner.


57-68: Well-implemented conditional rendering with tooltip.

The conditional rendering logic based on the presence of tooltipText is clean and effective. When tooltipText is provided, the Icon is wrapped in a Tooltip component with the appropriate props passed through.

web/src/beta/lib/reearth-ui/components/Popup/index.tsx (6)

7-8: New imports for enhanced functionality.

The addition of ShiftOptions and FloatingArrow imports from @floating-ui/react supports the new tooltip arrow functionality in the Popup component.


11-11: Updated import for theming.

The import has been expanded to include useTheme in addition to styled, which is necessary for accessing theme properties in the component.


67-107: Enhanced Content component with tooltip arrow support.

The Content component has been significantly enhanced to support rendering a tooltip arrow with proper theming. The changes include:

  1. Updated type definition to include the tooltip prop
  2. Accessing theme with useTheme
  3. Conditional rendering of FloatingArrow when tooltip is true
  4. Proper styling and positioning of the arrow using theme colors

This implementation follows best practices for conditional rendering and theming.


122-122: Added tooltip property to PopupProps type.

This correctly extends the component's API to support the new tooltip functionality.


134-134: Destructuring tooltip prop in Popup component.

The tooltip prop is properly destructured from the rest of the options, indicating it's handled separately from the props passed to the usePopover hook.


176-176: Correctly passing tooltip prop to Content component.

The tooltip prop is properly passed down to the Content component where it's used to conditionally render the FloatingArrow.

web/src/beta/lib/reearth-ui/components/Tabs/index.tsx (4)

1-6: Updated imports to include IconProps.

The import list has been updated to include IconProps, which is needed for the type extension of TabItem.


22-22: Well-implemented type extension using Pick utility.

The TabItem type is extended with specific properties from IconProps using the Pick utility type. This is a clean and type-safe way to reuse types without duplication.


86-86: Updated destructuring to include new properties.

The destructuring in the map function is updated to include the new tooltipText and placement properties, ensuring they're available for use in the rendered components.


96-98: Properly passing tooltip props to Icon component.

The tooltipText and placement props are correctly passed to the Icon component, enabling tooltip functionality for tab icons.

web/src/beta/features/Editor/Map/ToolsPanel/index.tsx (7)

1-1: Good addition of Placement import for tooltip positioning.

The import of Placement from "@floating-ui/react" enables proper typing for the tooltip placement property, ensuring type safety when specifying tooltip positions.


4-4: Nice addition of i18n support.

Adding the useT hook enables proper internationalization of the tooltip texts, making the UI more accessible to users of different languages.


13-14: Good enhancement of the SketchTool type.

The addition of tooltipText and optional placement properties to the SketchTool type properly reflects the enhanced functionality while maintaining type safety.


20-20: Appropriate use of translation hook.

The useT hook is correctly initialized at the component level to be used for all tooltip translations.


26-27: Well-structured tooltip implementation across all tools.

All sketch tools consistently include both tooltip text (properly translated) and placement properties. The tooltip texts are concise and descriptive, enhancing usability without cluttering the UI.

Also applies to: 33-34, 40-41, 47-48, 54-55, 61-62, 68-69, 75-76


80-80: Correctly updated dependency array.

The useMemo dependency array has been properly updated to include the t function, ensuring the sketch tools array is recalculated when the language changes.


90-103: Clean implementation of tooltip properties in the IconButton components.

The destructuring in the map function and passing of tooltip properties to IconButton components is well-implemented. The code remains readable while adding the new functionality.

server/pkg/asset/builder.go (1)

57-60: Clean implementation of Project method following the builder pattern.

The new Project method follows the consistent builder pattern seen throughout the codebase, properly setting the project field and returning the builder for method chaining.

server/internal/adapter/gql/gqlmodel/convert_asset.go (2)

12-16: Project ID handling logic looks correct.

The implementation properly handles the null case for project fields and follows existing patterns for ID conversion.


22-22: Correctly integrating ProjectID in returned Asset struct.

The ProjectID field is properly added to the returned Asset struct, maintaining consistency with the domain model changes.

server/pkg/asset/asset.go (2)

18-18: Asset struct properly extended with project field.

The new project field is added as a pointer type, which is appropriate as it appears to be an optional association.


34-40: Clean getter and setter methods for project field.

The implementation follows the existing pattern for getters and setters in this file. Both methods are simple and focused on their specific responsibilities.

server/e2e/gql_user_test.go (1)

28-28: Unused gateway.File parameter in baseSeederUser function.

The function signature has been updated to include a gateway.File parameter, but this parameter is not used anywhere in the function implementation.

#!/bin/bash
# Search for potential usage of the File parameter within the function
ast-grep --pattern 'func baseSeederUser($_, $_, $f) {
  $$$
  $f.$$$
  $$$
}'
server/go.mod (1)

133-133: Package moved to indirect dependencies

The github.com/oklog/ulid package has been moved from direct dependencies to indirect dependencies, indicating it's no longer directly imported by the codebase but is required by another dependency.

server/e2e/gql_validate_geojson_test.go (1)

186-186:

✅ Verification successful

Updated function call with new projectId parameter

The createAssetFromFileData function call has been updated to include a nil value for the new projectId parameter. This change aligns with the broader refactoring to support project associations in assets.


🏁 Script executed:

#!/bin/bash
# Check the function signature in the codebase
rg -n "func createAssetFromFileData" --type go

Length of output: 235


Verified: Updated Function Call Includes New projectId Parameter

  • The function signature in server/e2e/gql_asset_test.go now accepts a new projectId parameter (*string), confirming that the update is correct.
  • The test in server/e2e/gql_validate_geojson_test.go (line 186) correctly passes nil for projectId, aligning it with the refactoring changes made to support project associations.

The changes have been verified and are approved.

server/pkg/asset/id.go (1)

10-10:

✅ Verification successful

Added ProjectID type and related functions

Good additions of ProjectID type and helper functions. These follow the same pattern as the existing ID and WorkspaceID types in the file, maintaining consistency.

Also applies to: 14-14, 18-18, 22-22, 26-26


🏁 Script executed:

#!/bin/bash
# Verify where these ProjectID types are used to understand their integration
rg "ProjectID.*asset" --type go

Length of output: 1199


Verification Complete: ProjectID type integration approved

The new ProjectID alias is consistently implemented following the same pattern as existing ID types. Our search confirms that it’s correctly used throughout various components (e.g., in server/internal/infrastructure/memory/asset.go, server/internal/infrastructure/mongo/asset.go, and other asset-related files). There are no outstanding issues with this addition.

server/internal/infrastructure/fs/file_test.go (3)

116-118: Base URL for file repository changed

The base URL pattern for the file repository has been updated, moving from an assets-specific path (/assets) to the root path (/). This change simplifies the URL structure.


165-165: Base URL for file repository changed from assets path to root

Consistent with the earlier change, the base URL has been updated from https://example.com/assets to https://example.com/.


316-317:

✅ Verification successful

Base URL for URL parsing updated to root path

Consistent with the other URL changes in this file, the base URL for parsing has been updated from http://hoge.com/assets to http://hoge.com/.


🏁 Script executed:

#!/bin/bash
# Check how asset URLs are constructed in the file.go implementation
rg "getAssetFileURL" --type go -A 5 -B 5

Length of output: 3206


Confirmed: Base URL Update for URL Parsing

The test now correctly parses the base URL as "http://hoge.com/"—which is then used by getAssetFileURL to construct the full asset URL. The updated test snippet:

e, err := url.Parse("http://hoge.com/assets/xxx.yyy")
assert.NoError(t, err)
b, err := url.Parse("http://hoge.com/")
assert.NoError(t, err)
assert.Equal(t, e, getAssetFileURL(b, "xxx.yyy"))

accurately reflects the change, and the surrounding code in file.go corroborates that asset URLs are built by appending asset paths to the provided base. No further changes are needed.

server/internal/usecase/repo/asset.go (1)

20-20: Signature change aligns with project context integration.

The change from FindByWorkspace to FindByWorkspaceProject with the additional *id.ProjectID parameter enhances the asset repository interface to support project-specific asset retrieval. This change is consistent with the broader pattern of associating assets with specific projects rather than just workspaces.

server/gql/asset.graphql (6)

5-5: Added project association to Asset type.

Adding the projectId field to the Asset type is a good enhancement that will allow clients to understand which project an asset belongs to.


24-24: Project association during asset creation.

Adding the optional projectId field to the CreateAssetInput type allows clients to associate assets with specific projects during creation.


29-32: New UpdateAssetInput type for asset updates.

The new UpdateAssetInput type provides a clean interface for updating an asset's project association. The structure follows the GraphQL convention with an ID field for the entity to update and fields representing updatable properties.


49-52: New UpdateAssetPayload for mutation response.

The UpdateAssetPayload type follows GraphQL best practices by returning the updated entity's ID and the modified field(s). This allows clients to track the changes applied.


75-75: Project filtering for assets query.

Adding the optional projectId parameter to the assets query enhances the API by allowing clients to filter assets by project, providing more targeted data retrieval.


84-84: New updateAsset mutation.

The updateAsset mutation completes the CRUD operations for assets by providing the ability to update an asset's project association after creation.

server/internal/adapter/gql/gqlmodel/convert_project.go (3)

108-113: New ProjectExport struct for project data export.

The ProjectExport struct provides a clean data structure for exporting project data, including essential metadata like name, description, and image URL.


115-125: Conversion function for project export.

The ToProjectExport function properly handles nil input and maps project properties to the export structure.


127-137: Consider improving error handling and JSON conversion.

  1. Using json.MarshalIndent for conversion is unnecessary and less efficient than json.Marshal.
  2. Silently returning nil on errors makes debugging harder.

Apply this diff to improve the implementation:

func ToProjectExportFromJSON(data map[string]any) *ProjectExport {
	var p ProjectExport
-	bytes, err := json.MarshalIndent(data, "", "  ")
+	bytes, err := json.Marshal(data)
	if err != nil {
-		return nil
+		return nil // Consider logging the error or returning it
	}
	if err := json.Unmarshal(bytes, &p); err != nil {
-		return nil
+		return nil // Consider logging the error or returning it
	}
	return &p
}
server/e2e/gql_scene_test.go (2)

16-16: Object navigation improvement in tests.

The changes to use structured object navigation (r.Object().Value() instead of r.Value()) improve test clarity and consistency. This approach is more explicit about traversing the response object hierarchy.

Also applies to: 42-42, 71-73


77-100: New helper function for test project creation.

The createProjectWithExternalImage function is a valuable addition that encapsulates the logic for creating a project with an external image URL in tests. This promotes code reuse across test files.

server/internal/app/auth_client.go (2)

105-107: LGTM: Reduced noise in logging for e2e tests.

This is a good optimization to suppress verbose logging for e2e test users, making test output cleaner and easier to read.


115-117: LGTM: Consistent log filtering for e2e test users.

This change consistently applies the same log suppression logic for both user and operator details, ensuring clean test output.

server/internal/infrastructure/memory/asset.go (2)

49-49: Enhancement to asset filtering by adding project support.

The function has been renamed from FindByWorkspace to FindByWorkspaceProject and now accepts a project ID parameter, allowing for more granular asset filtering.


55-58: LGTM: Proper implementation of project-based asset filtering.

The implementation correctly handles both cases - when a project ID is provided and when it's not, maintaining backward compatibility while adding new functionality.

server/internal/infrastructure/fs/file.go (2)

113-113: Path validation strengthened for RemoveAsset.

The path validation now correctly checks against the full asset directory path rather than just the base URL path, improving security by ensuring files can only be removed from the designated asset directory.


286-290: URL generation improved for root path handling.

This change adds a special case for root path handling, ensuring proper URL construction when the base path is at the root level. This prevents potential issues with duplicate slashes or incorrect path joining.

server/internal/adapter/gql/resolver_query.go (1)

15-16: Fix inconsistent function name.

The loader call uses FindByWorkspace but the implementation uses FindByWorkspaceProject.

Apply this diff to fix the inconsistency:

-	return loaders(ctx).Asset.FindByWorkspace(ctx, teamID, projectId, keyword, gqlmodel.AssetSortTypeFrom(sortType), pagination)
+	return loaders(ctx).Asset.FindByWorkspaceProject(ctx, teamID, projectId, keyword, gqlmodel.AssetSortTypeFrom(sortType), pagination)
server/internal/adapter/gql/resolver_mutation_asset.go (2)

19-26: LGTM: CreateAsset now handles optional ProjectID well

The refactoring to handle an optional ProjectID is clean and follows good practices with proper error handling.


41-62: New UpdateAsset method looks good

The implementation correctly handles conversion between GraphQL and domain types, with appropriate error handling and use case delegation.

server/e2e/ping_test.go (1)

9-9: Code simplification approved

The test has been simplified by replacing explicit server configuration with a call to ServerPingTest(t), which makes the test more maintainable.

server/internal/app/app.go (2)

72-78: Auth middleware handling improved

The conditional handling of authentication middleware now properly supports disabled auth services, making the code more flexible.


164-168: AuthMiddlewareDummy implementation is clean

The dummy middleware implementation is simple and correctly implements the expected interface by passing requests directly to the next handler.

server/internal/usecase/interfaces/project.go (1)

74-75: Method renaming and type safety improvements

The interface methods have been renamed from ExportProject to ExportProjectData and ImportProject to ImportProjectData for clarity. Additionally, the ImportProjectData method now uses the more type-safe idx.ID[accountdomain.Workspace] instead of a string for the workspace ID parameter.

server/internal/adapter/gql/resolver_team.go (1)

27-28:

❓ Verification inconclusive

Function signature updated - ensure param consistency across resolvers

The function signature has been updated to include a new parameter projectID of type *gqlmodel.ID, which is now passed to the FindByWorkspace method of the loader. This aligns with changes in other resolver files to support filtering assets by project.

Make sure all places that call this resolver function have been updated to handle the new parameter.


🏁 Script executed:

#!/bin/bash
# Check if there are any calls to the Assets method that might be missing the projectID parameter
rg --type go "teamResolver\).Assets\(" -A 2 --glob "!*resolver_team.go"

Length of output: 73


Resolver Parameter Consistency Verification

The function signature update in server/internal/adapter/gql/resolver_team.go was intended to introduce the new projectID parameter and pass it to the FindByWorkspace loader. Our automated grep search (excluding resolver_team.go) produced no instances of calls to Assets that might be missing this parameter. However, since the returned output was empty and could indicate either that no external call sites exist or that further inspection is needed, please verify manually that all invocations of Assets (outside the resolver file) have been updated accordingly.

  • Action Item: Manually inspect the codebase for any calls to teamResolver.Assets to ensure that they pass the projectID parameter.
  • Snippet Reference:
    func (r *teamResolver) Assets(ctx context.Context, obj *gqlmodel.Team, projectID *gqlmodel.ID, first *int, last *int, after *usecasex.Cursor, before *usecasex.Cursor) (*gqlmodel.AssetConnection, error) {
        return loaders(ctx).Asset.FindByWorkspace(ctx, obj.ID, projectID, nil, nil, &gqlmodel.Pagination{
server/internal/infrastructure/mongo/asset.go (3)

67-81: Function renamed with enhanced filtering capability

The function has been renamed from FindByWorkspace to FindByWorkspaceProject with an additional parameter projectId. The implementation now supports filtering assets by either project ID (when provided) or workspace ID (as fallback).

This is a good enhancement that allows more granular querying of assets.


84-85: Filter construction simplified

The keyword filter assignment has been simplified to directly assign the regex condition to filter["name"] instead of using the mongox.And function, which improves readability.


96-100: Handle type assertion failure case

The type assertion to bson.M could silently fail if the assertion is unsuccessful. Consider handling the failure case explicitly.

Apply this diff to handle the failure case:

-	if andFilter, ok := mongox.And(filter, "url", bson.M{
-		"$regex": primitive.Regex{Pattern: bucketPattern, Options: "i"},
-	}).(bson.M); ok {
-		filter = andFilter
-	}
+	if andResult := mongox.And(filter, "url", bson.M{
+		"$regex": primitive.Regex{Pattern: bucketPattern, Options: "i"},
+	}); andResult != nil {
+		if andFilter, ok := andResult.(bson.M); ok {
+			filter = andFilter
+		}
+	}
server/internal/infrastructure/mongo/mongodoc/asset.go (5)

17-17: Added project association to AssetDocument

Added a new field Project to support associating assets with specific projects. This is a good addition that enhances the flexibility of the asset model.


35-41: Project ID extraction added to NewAsset function

The function now properly extracts the project ID from the asset and includes it in the document when present. The extraction logic handles the optional nature of the project field correctly.


46-46: Set Project field in AssetDocument constructor

The Project field is now set properly in the document constructor, completing the implementation of the project association feature.


65-73: Model conversion for project field

Good implementation of the project ID conversion in the Model method. The code properly handles the optional nature of the project field and includes appropriate error handling during the conversion process.


78-78: Include project in asset builder

The project ID is now included in the asset builder, completing the bidirectional conversion between model and document representations.

server/internal/adapter/gql/loader_asset.go (4)

39-39: Function signature updated to include project ID

The function signature now includes proId *gqlmodel.ID parameter to support filtering assets by project. This change aligns with the modifications in other parts of the codebase.


45-53: Project ID conversion added

The function now properly converts the optional project ID from the GraphQL model to the internal domain model. The conversion includes appropriate error handling for invalid IDs.


54-54: Updated function call to use the project-aware implementation

Replaced the call to FindByWorkspace with FindByWorkspaceProject to utilize the new functionality. This ensures that asset filtering by project is properly supported.


60-66: Optimized edge creation

The edge creation code has been optimized by pre-allocating the slice with the exact size needed and directly assigning values instead of using append. This is a small but good performance improvement.

server/internal/usecase/interactor/scene.go (4)

21-21: No concerns with the import.


668-671: Guard clause for story retrieval is well-implemented.
No issues here.


728-730: URL checks are consistent.
No issues here.


753-755: URL checks are consistent.
No issues here.

server/internal/usecase/interfaces/asset.go (3)

28-28: New ProjectID field is clearly expressed.
No issues here.


39-39: New method for filtered asset retrieval by project is clear.
No issues here.


43-43: Revised ImportAssetFiles signature looks consistent.
No issues here.

server/e2e/gql_project_export_import_test.go (19)

4-6: Updated imports are valid and aligned with the new references.

Also applies to: 8-8, 12-13


19-19: Note for test usage is fine.


21-21: Descriptive test name enhances clarity.


23-23: Full seeder is used for comprehensive test data.


25-26: Retrieving seeded data is straightforward.


28-29: Export procedure invocation is clear.


31-32: Import procedure call seamlessly follows export.


34-40: Assertions for presence of keys in the import result are robust.


42-43: Validating project name and description further ensures correctness.


45-45: Extracting the new scene ID for subsequent checks is well thought out.


48-48: Retrieving the newly imported scene for comparison is correct.


50-58: Comparisons of each section of scene data ensure accuracy.


59-63: Cleaning up the exported file is good test hygiene.


66-70: convertLine function is concise and handles line-by-line marshalling neatly.


72-102: compareValue function robustly checks diffs while ignoring known dynamic fields.


104-111: isIgnore helper clarifies ignoring certain fields.


113-120: isID function is specialized but sufficient for widget identifiers.


143-143: importProject function confirms correct import flow.
No further issues noted.

Also applies to: 168-168, 170-170, 172-172


175-175: getScene function uses GraphQL query effectively for final comparison.

Also applies to: 185-185, 187-187, 189-189, 190-190

server/gql/workspace.graphql (3)

8-14: Addition of projectId parameter enhances asset filtering capability.

The addition of the projectId parameter to the assets field allows for filtering assets by their associated project, which aligns with the overall goal of improving component parameterization. This change enables more precise asset querying and management.


15-21: Code formatting improvement for better readability.

Breaking down the parameters into separate lines improves code readability while maintaining the same functionality.


121-123: Code formatting improvement for better readability.

Consistent with other formatting changes, this improves readability of the GraphQL schema.

server/e2e/gql_asset_test.go (10)

15-16: Updated test function name reflects its specific purpose.

Renaming from TestGetAssets to TestCoreSupportGetAssets makes the test's focus more explicit.


17-18: Simplified test setup using ServerAndRepos.

The test now uses a more streamlined setup approach with ServerAndRepos rather than manual configuration.


22-76: Comprehensive asset creation test with JSON structure validation.

The test now creates assets with different configurations (PNG/CSV files, with/without core support) and validates the full JSON structure of the response using the new JSONEqRegexpValue helper. The inclusion of projectId: null verification is particularly important for testing the new field.


106-107: Updated repository method to support project filtering.

The asset repository's FindByWorkspaceProject method now accepts a project ID parameter, aligning with the GraphQL schema changes.


114-115: Updated assets query to include project filtering capability.

The assets retrieval now supports filtering by project ID, which corresponds to the schema changes.


124-179: Comprehensive test for project-asset associations.

This new test thoroughly validates:

  1. Creating assets associated with specific projects
  2. Retrieving assets filtered by project
  3. Moving assets between projects
  4. Removing project associations
  5. Asset deletion

The test provides good coverage for the new project association functionality.


181-198: Updated CreateAsset mutation to support project associations.

The mutation now includes projectId as an optional parameter, allowing assets to be associated with specific projects during creation.


200-222: Updated createAsset function to support project associations.

The function now accepts a projectId parameter, enabling test cases to associate assets with specific projects.


258-285: Updated createAssetFromFileData to support project associations.

This function now accepts a projectId parameter, maintaining consistency with the createAsset function.


287-350: Enhanced getAssets query with project filtering support.

The function now accepts a projectId parameter and includes it in the GraphQL query, enabling filtering of assets by project. The query structure has been updated to accommodate this new parameter.

server/internal/adapter/gql/generated.go (6)

8101-8111: Google Analytics fields added to Story type

The PR successfully adds the required Google Analytics fields to the Story type in the GraphQL schema:

  • enableGa: Boolean! - Flag to enable/disable Google Analytics
  • trackingId: String! - The GA tracking ID

These additions align with the PR objectives to support Google Analytics integration in the Story component.


8173-8175: Google Analytics fields added to UpdateStoryInput

The PR correctly adds the Google Analytics fields to the UpdateStoryInput type as nullable fields:

  • enableGa: Boolean
  • trackingId: String

Making these fields nullable in the update input is appropriate as it allows for partial updates.


51899-51900: Handling of new GA fields in unmarshalInputUpdateStoryInput

The code correctly updates the marshaling/unmarshaling logic to handle the new Google Analytics fields in the UpdateStoryInput. The field ordering includes the new fields and the unmarshaling code handles type conversion appropriately.

Also applies to: 52011-52025


61476-61485: Field resolver implementation for Story GA fields

The field resolver implementation for the Story type correctly handles the new Google Analytics fields. The implementation ensures the fields are properly resolved and validates that non-nullable fields are actually present.


6978-6981: New Asset update functionality added

In addition to the Google Analytics support, this PR also introduces asset update functionality with:

  • New UpdateAssetInput with assetId and optional projectId fields
  • New UpdateAssetPayload return type

These additions enhance the asset management capabilities of the application.

Also applies to: 6998-7001


41920-42006:

❓ Verification inconclusive

Verify field dependencies in business logic

While the GraphQL schema implementation looks correct, ensure that the business logic handles the relationship between enableGa and trackingId appropriately.

For example, if enableGa is false, your application should likely ignore the trackingId value. Consider validating in your resolver implementation that when enableGa is true, a valid trackingId is provided.


🏁 Script executed:

#!/bin/bash
# Description: Check if the resolver implementation validates tracking ID when GA is enabled

# Look for validation logic in story-related files
echo "Searching for validation logic in resolver implementations..."
rg -A 10 -B 10 "func.*Update.*Story" --type go | rg -A 10 -B 10 "enableGa|trackingId"

Length of output: 1735


Attention: Validate GA Flag and Tracking ID in Business Logic

The current generated input unmarshal function (unmarshalInputUpdateStoryInput in server/internal/adapter/gql/generated.go) parses both the enableGa and trackingId fields without enforcing their logical dependency. Please ensure that:

  • If enableGa is false, any supplied trackingId is ignored.
  • If enableGa is true, a valid (non-empty and correctly formatted) trackingId is required.

This validation should be handled within your resolver or business logic layer rather than relying solely on the generated input unmarshalling.

server/internal/usecase/interactor/asset.go (2)

88-99: Validate workspace-to-project consistency
Similar to a previous suggestion, ensure that if inp.ProjectID is provided, it belongs to the same workspace inp.WorkspaceID.


108-127: Validate workspace-to-project consistency
Similar to a previous suggestion, ensure the user can also write to the assigned pid if it's different from the current asset's project.

server/internal/adapter/gql/resolver_mutation_project.go (9)

130-130: Looks good
No concerns for this line.


155-155: ExportProjectData usage
This call to ExportProjectData is consistent with the new approach.


183-183: Verify exported asset scope
Here, you pass nil for pid, which exports all workspace assets instead of just the project's assets. Confirm if that is intended or needed.


189-189: LGTM
Parsing the asset URL is correct.


214-214: Handle potential workspace ID conversion error
Previously reported. Properly handle the error from accountdomain.WorkspaceIDFrom(string(input.TeamID)).


224-224: Handle JSON unmarshal errors
Previously mentioned. Please avoid discarding errors when calling unmarshalProject(tempData).


349-349: Standardize error handling
Check for and handle JSON unmarshal errors when retrieving project from the data.


359-359: Standardize error handling
Check for and handle JSON unmarshal errors when retrieving assets from the data.


375-375: Standardize error handling
Check for and handle JSON unmarshal errors when retrieving plugins and scene from the data.

server/internal/usecase/interactor/project.go (6)

513-513: Refactored export method usage
No issues with this new function.


526-526: Approved usage
This call to AddZipAsset is consistent with the introduced helper method.


566-566: Add workspace validation
Previously suggested. Validate the workspace is not nil or invalid before proceeding.


573-573: Add project data validation
Previously suggested. Confirm the returned input from jsonmodel.ToProjectExportFromJSON is valid.


579-588: Confirm ignoring imported fields
All imported fields for Alias, Archived, and CoreSupport are overridden with static values. Verify whether this is intentional or if you want to preserve the imported values.


627-649: Improve error handling
Please see prior suggestions to handle errors more robustly in AddZipAsset.

server/e2e/gql_storytelling_test.go (4)

57-152: Comprehensive page-level CRUD tests.
The test logic systematically covers creation, duplication, fetching, updating—including error handling—and deletion of pages. Everything appears to be in order, and it provides good coverage of edge cases such as invalid page IDs. Keep it up!


154-207: Robust block-level CRUD verification.
Creating, moving, and removing blocks is well-structured here. The checks for final block positions after reordering are neat and ensure correctness. No issues found.


208-229: Effective property update checks for page blocks.
Updating and retrieving block properties with different field types (STRING, SPACING) is well-covered. Everything looks good.


869-904: Removal of blocks verified correctly.
The final array check with NotConsistsOf(blockID) neatly ensures the block is indeed removed. This change is well-implemented.

server/e2e/seeder.go (2)

121-164: Revisit asset file validation.
A past review advised adding checks for file size and content type in the addAsset function. This can prevent accidental handling of invalid or oversized uploads.
[duplicate_comment, refactor_suggestion_good_to_have]


203-216: Fix critical error checks.
These lines use err != err instead of err != nil, causing the condition to never be true. This is a known issue highlighted in previous reviews. Update them as follows to properly handle errors:
[duplicate_comment, critical_issue]

-if err = r.Scene.Save(ctx, s); err != err {
+if err = r.Scene.Save(ctx, s); err != nil {
    return err
}

(Apply similarly in the subsequent checks in lines 205, 207, 209, 211, 213, 215.)

@@ -144,7 +166,7 @@ func (i *Asset) UploadAssetFile(ctx context.Context, name string, zipFile *zip.F
contentType := http.DetectContentType([]byte(zipFile.Name))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use actual file contents for content type detection
Currently, http.DetectContentType([]byte(zipFile.Name)) checks the filename rather than the file contents, leading to inaccurate content type detection.

Apply this fix to read the first 512 bytes of the file:

- contentType := http.DetectContentType([]byte(zipFile.Name))
+ buf := make([]byte, 512)
+ n, err := readCloser.Read(buf)
+ if err != nil && err != io.EOF {
+   return nil, 0, fmt.Errorf("error reading file header: %w", err)
+ }
+ // Reopen file to reset reader position for further operations
+ if cerr := readCloser.Close(); cerr != nil {
+   return nil, 0, fmt.Errorf("error closing file after header read: %w", cerr)
+ }
+ reopened, err := zipFile.Open()
+ if err != nil {
+   return nil, 0, fmt.Errorf("error reopening file: %w", err)
+ }
+ defer reopened.Close()
+ contentType := http.DetectContentType(buf[:n])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
contentType := http.DetectContentType([]byte(zipFile.Name))
buf := make([]byte, 512)
n, err := readCloser.Read(buf)
if err != nil && err != io.EOF {
return nil, 0, fmt.Errorf("error reading file header: %w", err)
}
// Reopen file to reset reader position for further operations
if cerr := readCloser.Close(); cerr != nil {
return nil, 0, fmt.Errorf("error closing file after header read: %w", cerr)
}
reopened, err := zipFile.Open()
if err != nil {
return nil, 0, fmt.Errorf("error reopening file: %w", err)
}
defer reopened.Close()
contentType := http.DetectContentType(buf[:n])

@pyshx pyshx changed the title feat(server): storytelling ga support feat(server): storytelling ga support [VIZ-1180] Feb 26, 2025
@hexaforce hexaforce force-pushed the feat/storytelling-ga-support branch from 07ea4c4 to 47c869e Compare February 26, 2025 07:39
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
server/e2e/gql_asset_test.go (2)

224-240: 🛠️ Refactor suggestion

Add error handling for updateAsset response.

The function should check for errors in the GraphQL response.

Apply this diff to add error handling:

 func updateAsset(e *httpexpect.Expect, assetId string, projectId *string) *httpexpect.Value {
     requestBody := GraphQLRequest{
         OperationName: "UpdateAsset",
         Query: `mutation UpdateAsset($assetId: ID!, $projectId: ID) {
             updateAsset(input: { assetId: $assetId projectId: $projectId }) {
                 assetId
                 projectId
+                errors {
+                    message
+                    path
+                }
                 __typename
             }
         }`,
         Variables: map[string]any{
             "assetId":   assetId,
             "projectId": projectId,
         },
     }
     return Request(e, uID.String(), requestBody)
 }

242-256: 🛠️ Refactor suggestion

Add error handling for removeAsset response.

Similar to updateAsset, this function should also check for errors.

Apply this diff:

 func removeAsset(e *httpexpect.Expect, assetId string) *httpexpect.Value {
     requestBody := GraphQLRequest{
         OperationName: "RemoveAsset",
         Query: `mutation RemoveAsset($assetId: ID!) {
             removeAsset(input: { assetId: $assetId }) {
                 assetId
+                errors {
+                    message
+                    path
+                }
                 __typename
             }
         }`,
         Variables: map[string]any{
             "assetId": assetId,
         },
     }
     return Request(e, uID.String(), requestBody)
 }
server/e2e/common.go (1)

266-271: 🛠️ Refactor suggestion

Add type assertion error handling in JSONEqRegexpValue.

The type assertion in JSONEqRegexpValue should handle the case where the assertion fails.

Example refactor:

 func JSONEqRegexpValue(t *testing.T, actual *httpexpect.Value, expected string) bool {
-    if actualData, ok := actual.Raw().(map[string]interface{}); ok {
+    actualData, ok := actual.Raw().(map[string]interface{})
+    if !ok {
+        t.Errorf("expected map[string]interface{}, got %T", actual.Raw())
+        return false
+    }
     return JSONEqRegexpInterface(t, actualData, expected)
-    }
-    return false
 }
🧹 Nitpick comments (1)
server/e2e/gql_storytelling_test.go (1)

869-869: Modified block removal assertion syntax.

The assertion for block removal now uses NotConsistsOf instead of the previous implementation. The change maintains the same test logic but uses a slightly different assertion method.

Consider using NotContains for simpler and more direct assertion:

-	res.Object().
-		Path("$.data.removeStoryBlock.page.blocks[:].id").Array().NotConsistsOf(blockID)
+	res.Object().
+		Path("$.data.removeStoryBlock.page.blocks[:].id").Array().NotContains(blockID)

Also applies to: 900-901

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07ea4c4 and 47c869e.

📒 Files selected for processing (20)
  • server/e2e/common.go (7 hunks)
  • server/e2e/gql_asset_test.go (6 hunks)
  • server/e2e/gql_me_test.go (1 hunks)
  • server/e2e/gql_project_test.go (6 hunks)
  • server/e2e/gql_storytelling_test.go (6 hunks)
  • server/e2e/mock_test.go (1 hunks)
  • server/e2e/ping_test.go (1 hunks)
  • server/gql/storytelling.graphql (3 hunks)
  • server/internal/adapter/gql/generated.go (61 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (5 hunks)
  • server/internal/adapter/gql/resolver_mutation_storytelling.go (1 hunks)
  • server/internal/app/app.go (2 hunks)
  • server/internal/app/auth_client.go (1 hunks)
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go (4 hunks)
  • server/internal/usecase/interactor/storytelling.go (3 hunks)
  • server/internal/usecase/interactor/storytelling_test.go (1 hunks)
  • server/internal/usecase/interfaces/story.go (1 hunks)
  • server/pkg/storytelling/story.go (4 hunks)
  • server/pkg/storytelling/story_bulider.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (16)
  • server/e2e/mock_test.go
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go
  • server/internal/usecase/interfaces/story.go
  • server/internal/adapter/gql/resolver_mutation_storytelling.go
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go
  • server/internal/usecase/interactor/storytelling_test.go
  • server/e2e/gql_project_test.go
  • server/gql/storytelling.graphql
  • server/internal/app/app.go
  • server/e2e/ping_test.go
  • server/e2e/gql_me_test.go
  • server/internal/app/auth_client.go
  • server/internal/usecase/interactor/storytelling.go
  • server/pkg/storytelling/story.go
  • server/pkg/storytelling/story_bulider.go
  • server/internal/adapter/gql/gqlmodel/models_gen.go
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Redirect rules - reearth-web
  • GitHub Check: Header rules - reearth-web
  • GitHub Check: Pages changed - reearth-web
🔇 Additional comments (16)
server/e2e/gql_storytelling_test.go (4)

49-52: Google Analytics parameters correctly verified in story response.

The test now properly validates the new GA parameters, ensuring that when enableGa is set to true and trackingId is set to "test-tracking-id" during story update, these values are correctly reflected in the API response.


254-256: Google Analytics fields correctly added to the published story JSON.

The test correctly verifies that the GA parameters are included in the published story JSON output, with default values when not explicitly set (enableGa: false and trackingId: "").

Also applies to: 367-368


513-514: Google Analytics fields added to the GraphQL query.

The query now fetches the new Google Analytics parameters (enableGa and trackingId) from the story, aligning with the PR objectives.


564-565: UpdateStory mutation updated to include GA parameters.

The mutation signature has been correctly updated to include the new Google Analytics parameters, satisfying the PR objective of adding GA support to the Story component.

Also applies to: 581-582

server/e2e/gql_asset_test.go (2)

122-179: Comprehensive project-asset association test added.

The new test function properly verifies:

  1. Creating assets with project associations
  2. Fetching assets by project ID
  3. Moving assets between projects
  4. Removing project associations
  5. Removing assets

This is a valuable addition that ensures the project-asset relationship functionality works correctly.


181-184: CreateAssetMutation updated to support projectId parameter.

The mutation has been correctly modified to include the optional projectId parameter, allowing assets to be associated with projects during creation.

server/e2e/common.go (4)

35-43: Auth configuration refactored into a reusable variable.

Good refactoring to extract the commonly used auth configuration settings into a reusable variable, which improves code maintainability and reduces duplication.


45-45: Seeder function signature updated to include file gateway.

The Seeder type now includes the file gateway parameter, which allows tests to properly initialize and use file-related functionality. This change is consistent across the codebase.


143-156: Server initialization streamlined with better function names.

The refactoring of server initialization functions improves code readability with more descriptive function names (startServer, ServerAndRepos, Server) and reduces code duplication by centralizing configuration.


284-296: ValueDump function improved with better type handling.

The function now properly handles different types of data using a type switch, making it more robust when debugging different response structures.

server/internal/adapter/gql/generated.go (6)

8100-8111: Google Analytics fields added to Story type as required

This change adds the two new Google Analytics fields to the Story type:

  • enableGa: Boolean! - Flag to enable/disable Google Analytics
  • trackingId: String! - The Google Analytics tracking ID

These changes align with the PR objectives to integrate Google Analytics parameters into the Story component.


8169-8175: Google Analytics fields correctly added to UpdateStoryInput

The implementation properly adds the Google Analytics fields to the UpdateStoryInput:

  • enableGa: Boolean - Optional field to update GA enablement
  • trackingId: String - Optional field to update the GA tracking ID

These changes align with the PR objectives and follow the proper pattern of making fields optional in update inputs.


41920-42006: Field resolvers implemented for new GA fields

The resolvers for _Story_enableGa and _Story_trackingId have been properly implemented with appropriate error handling and null checking.

The implementation appears to be complete and matches the schema definition.


52011-52025: Input unmarshalling logic added for GA fields

The unmarshalling logic correctly handles the new enableGa and trackingId fields in the UpdateStoryInput, with appropriate error handling and path context.

This implementation will properly parse incoming GraphQL requests that include these fields.


6951-7001: Asset-Project relationship enhancements

In addition to the GA support, this PR adds Project relationship to Assets:

  • Adds projectId field to Asset type
  • Creates new UpdateAssetInput and UpdateAssetPayload types
  • Enables associating assets with specific projects

While not explicitly mentioned in the PR objectives, these changes provide improved asset management capabilities.


7021-7035: GraphQL operations extended for asset management

The implementation properly extends:

  1. The Query type with projectId filtering for assets
  2. The Mutation type with updateAsset operation

These changes enable more targeted asset queries and management capabilities, which complement the Story improvements.

@hexaforce hexaforce force-pushed the feat/storytelling-ga-support branch 3 times, most recently from cf9580b to fb9a4ff Compare February 27, 2025 08:34
fix permmision error

associate project asset

remove comment

fix test error

Undo comment out

Passing GA parameters
@hexaforce hexaforce force-pushed the feat/storytelling-ga-support branch from fb9a4ff to d6707d8 Compare February 27, 2025 08:39
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

♻️ Duplicate comments (5)
server/internal/infrastructure/mongo/asset.go (1)

96-100: 🛠️ Refactor suggestion

Handle type assertion failure case.

The type assertion to bson.M could silently fail if the assertion is unsuccessful. Consider handling the failure case explicitly.

Apply this diff to handle the failure case:

-	if andFilter, ok := mongox.And(filter, "url", bson.M{
-		"$regex": primitive.Regex{Pattern: bucketPattern, Options: "i"},
-	}).(bson.M); ok {
-		filter = andFilter
-	}
+	if andResult := mongox.And(filter, "url", bson.M{
+		"$regex": primitive.Regex{Pattern: bucketPattern, Options: "i"},
+	}); andResult != nil {
+		if andFilter, ok := andResult.(bson.M); ok {
+			filter = andFilter
+		}
+	}
server/e2e/gql_project_export_import_test.go (1)

122-141: ⚠️ Potential issue

Add error handling for HTTP response in exporProject.

The function should check the HTTP response status and handle potential errors before writing to file.

Apply this diff to improve error handling:

 downloadResponse := e.GET(fmt.Sprintf("http://localhost:8080%s", downloadPath)).
   Expect().
   Status(http.StatusOK).
   Body().Raw()
+if len(downloadResponse) == 0 {
+  t.Fatal("Empty response from export endpoint")
+}
 fileName := "project_data.zip"
 err := os.WriteFile(fileName, []byte(downloadResponse), os.ModePerm)
 assert.Nil(t, err)
server/internal/usecase/interactor/asset.go (2)

108-127: ⚠️ Potential issue

Validate workspace-to-project consistency.

When updating an existing asset's project, the code does not explicitly confirm the new project is in the same workspace or that the operator can write to it. This might allow transferring an asset to a project in a workspace where the user lacks permission.

Apply this fix to validate the target project:

 func (i *Asset) Update(ctx context.Context, aid id.AssetID, pid *id.ProjectID, operator *usecase.Operator) (id.AssetID, *id.ProjectID, error) {
 	return Run2(
 		ctx, operator, i.repos,
 		Usecase().Transaction(),
 		func(ctx context.Context) (id.AssetID, *id.ProjectID, error) {
 			asset, err := i.repos.Asset.FindByID(ctx, aid)
 			if err != nil {
 				return aid, pid, err
 			}

 			if ok := operator.IsWritableWorkspace(asset.Workspace()); !ok {
 				return aid, pid, interfaces.ErrOperationDenied
 			}

+			// Verify the target project is valid and operator has permissions
+			if pid != nil {
+				proj, err := i.repos.Project.FindByID(ctx, *pid)
+				if err != nil {
+					return aid, pid, err
+				}
+				if proj == nil {
+					return aid, pid, interfaces.ErrNotFound
+				}
+				// Check if project is in the same workspace as the asset
+				if proj.Workspace() != asset.Workspace() {
+					return aid, pid, interfaces.ErrOperationDenied
+				}
+				// Check if operator has write permissions to the project's workspace
+				if !operator.IsWritableWorkspace(proj.Workspace()) {
+					return aid, pid, interfaces.ErrOperationDenied
+				}
+			}
+
 			asset.SetProject(pid)

 			return aid, pid, i.repos.Asset.Save(ctx, asset)
 		},
 	)
 }

166-166: ⚠️ Potential issue

Use actual file contents for content type detection.

Currently, http.DetectContentType([]byte(zipFile.Name)) checks the filename rather than the file contents, leading to inaccurate content type detection.

Apply this fix to read the first 512 bytes for proper content type detection:

-contentType := http.DetectContentType([]byte(zipFile.Name))
+buf := make([]byte, 512)
+n, err := readCloser.Read(buf)
+if err != nil && err != io.EOF {
+  return nil, 0, fmt.Errorf("error reading file header: %w", err)
+}
+// Need to re-open the file since we've now read part of it
+if cerr := readCloser.Close(); cerr != nil {
+  return nil, 0, fmt.Errorf("error closing file after header read: %w", cerr)
+}
+readCloser, err = zipFile.Open()
+if err != nil {
+  return nil, 0, fmt.Errorf("error reopening file after content detection: %w", err)
+}
+contentType := http.DetectContentType(buf[:n])

Don't forget to add the import for io package:

import (
	...
+	"io"
	...
)
server/e2e/seeder.go (1)

203-223: ⚠️ Potential issue

Critical error: if err != err condition never succeeds.

These lines compare err to itself, causing the error handling block to never run. This effectively bypasses error handling. Replace with if err != nil to correctly handle potential errors.

-	if err = r.Scene.Save(ctx, s); err != err {
+	if err = r.Scene.Save(ctx, s); err != nil {
     return err
 }
-	if err = r.Property.Save(ctx, prop); err != err {
+	if err = r.Property.Save(ctx, prop); err != nil {
     return err
 }
-	if err = r.Layer.Save(ctx, rootLayer); err != err {
+	if err = r.Layer.Save(ctx, rootLayer); err != nil {
     return err
 }
-	if err = addWidget(ctx, s, r); err != err {
+	if err = addWidget(ctx, s, r); err != nil {
     return err
 }
-	if err = addStory(ctx, r); err != err {
+	if err = addStory(ctx, r); err != nil {
     return err
 }
-	if err = addLayerSimple(ctx, r); err != err {
+	if err = addLayerSimple(ctx, r); err != nil {
     return err
 }
-	if err = addLayerStyle(s.ID(), ctx, r); err != err {
+	if err = addLayerStyle(s.ID(), ctx, r); err != nil {
     return err
 }
🧹 Nitpick comments (6)
server/internal/adapter/gql/resolver_mutation_asset.go (1)

41-62: New UpdateAsset method with project association.

The method allows updating an asset's project association, but there's an unused variable.

On line 48-49, you define project := input.ProjectID but then directly use input.ProjectID on line 49. Consider using the local variable:

-	if project := input.ProjectID; project != nil {
-		pidValue, err := gqlmodel.ToID[id.Project](*input.ProjectID)
+	if project := input.ProjectID; project != nil {
+		pidValue, err := gqlmodel.ToID[id.Project](*project)
server/internal/usecase/interactor/project.go (1)

513-533: Consider consolidating image export checks.

The new ExportProjectData function includes logic for handling the project's image asset. As a future improvement, consider consolidating potential asset export checks (e.g., verifying if an image URL is valid) to prevent partial exports if the image asset is unresolvable.

server/internal/adapter/gql/gqlmodel/models_gen.go (1)

138-138: Add clarity on optional ProjectID in Asset.

The new ProjectID field is optional, but its usage might be critical in certain workflows. Consider elaborating in documentation or resolvers on whether this field is mandatory in practice and how it’s handled if omitted.

server/e2e/gql_storytelling_test.go (2)

15-55: Comprehensive Story CRUD coverage.

This test thoroughly exercises create, fetch, update, and delete story flows, including the new GA fields (enableGa/trackingId). Good coverage. Consider a negative test to verify behavior when invalid GA parameters are provided.


57-152: Robust Page CRUD verification.

The TestStoryPageCRUD method tests page creation, duplication, and deletion with thorough checks. Tests appear comprehensive. Potential improvement: add coverage for zero or negative indices.

server/e2e/gql_asset_test.go (1)

17-18: Improve test name or add descriptive comment.

The function name TestCoreSupportGetAssets vaguely hints at testing assets with core support, but the actual test logic also creates assets with coreSupport = false. Consider either renaming the test (e.g., TestGetAssetsWithAndWithoutCoreSupport) or clarifying via inline comments that both scenarios are tested.

🛑 Comments failed to post (2)
server/internal/usecase/interactor/nlslayer.go (1)

1154-1158: 💡 Verification agent

🧩 Analysis chain

Verify all references to the new feature creation pattern

This change from implicit to explicit ID generation is a good improvement, but ensure all other places in the codebase that may have used the old pattern (NewFeatureWithNewId) have been updated.


🏁 Script executed:

#!/bin/bash
# Search for any remaining instances of NewFeatureWithNewId

echo "Looking for any remaining usages of NewFeatureWithNewId pattern..."
rg "NewFeatureWithNewId" --type go

echo "Checking all feature creation patterns to verify consistency..."
rg "NewFeature\\(" --type go -A 2

Length of output: 8435


Action Required: Update Test Cases for Feature Creation Pattern

The review verifies that the production code now consistently uses the new explicit ID generation approach via NewFeature with NewFeatureID(). However, the shell script output still shows an instance in the test file (server/pkg/nlslayer/feature_test.go) with a function named TestNewFeatureWithNewId. Please update these tests to use the new pattern for consistency.

server/e2e/common.go (1)

35-43: 🛠️ Refactor suggestion

Global variable usage could lead to shared state between tests.

fr is declared as a package-level variable, which may cause conflicts or unexpected behavior if tests run in parallel. Consider localizing this variable within functions or test setups to avoid potential concurrency issues.

-var (
-	fr                 *gateway.File
-	disabledAuthConfig = &config.Config{...}
-)
+var disabledAuthConfig = &config.Config{...}
// ...
// Pass gateway.File around instead of storing in 'fr' at package level.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

var disabledAuthConfig = &config.Config{
	Origins: []string{"https://example.com"},
	AuthSrv: config.AuthSrvConfig{
		Disabled: true,
	},
}

// Pass gateway.File around instead of storing in 'fr' at package level.

@hexaforce hexaforce merged commit 6a8820f into main Feb 27, 2025
19 checks passed
@hexaforce hexaforce deleted the feat/storytelling-ga-support branch February 27, 2025 08:48
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
server/e2e/gql_storytelling_test.go (6)

15-55: Consider verifying final deletion in TestStoryCRUD.

This test thoroughly validates creating, fetching, updating, and deleting a story. As a good-to-have improvement, consider performing a final fetch after the deletion to confirm that the story is truly removed from the scene.


57-152: Split TestStoryPageCRUD into smaller tests for maintainability.

Currently, this single test covers creation, duplication, updating, and deletion. Splitting these operations into separate tests (e.g., TestCreatePage, TestDuplicatePage, etc.) can enhance clarity and simplify debugging.


154-206: Add negative scenario coverage for block operations.

The test covers creating, moving, and removing blocks successfully. Consider adding negative tests for invalid block IDs or out-of-range index positions to ensure the code handles error paths gracefully.


208-229: Ensure property schema coverage in TestStoryPageBlocksProperties.

These property updates and fetches look thorough. Consider testing error cases, such as invalid property types or missing required fields, to strengthen validation coverage.


231-413: Consider externalizing large JSON in TestStoryPublishing.

Storing your extensive expected JSON in a separate fixture file can make this test more readable and easier to maintain, especially when the structure changes frequently.


869-904: Ensure error handling for removeBlock.

The updated function removes a block and checks it no longer exists. Consider testing removal of an already-removed or invalid block to confirm robust error handling and prevent unwanted side effects.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb9a4ff and d6707d8.

📒 Files selected for processing (20)
  • server/e2e/common.go (2 hunks)
  • server/e2e/gql_asset_test.go (2 hunks)
  • server/e2e/gql_me_test.go (1 hunks)
  • server/e2e/gql_project_test.go (2 hunks)
  • server/e2e/gql_storytelling_test.go (6 hunks)
  • server/e2e/mock_test.go (1 hunks)
  • server/e2e/ping_test.go (1 hunks)
  • server/gql/storytelling.graphql (3 hunks)
  • server/internal/adapter/gql/generated.go (20 hunks)
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go (1 hunks)
  • server/internal/adapter/gql/gqlmodel/models_gen.go (2 hunks)
  • server/internal/adapter/gql/resolver_mutation_storytelling.go (1 hunks)
  • server/internal/app/app.go (2 hunks)
  • server/internal/app/auth_client.go (1 hunks)
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go (3 hunks)
  • server/internal/usecase/interactor/storytelling.go (3 hunks)
  • server/internal/usecase/interactor/storytelling_test.go (1 hunks)
  • server/internal/usecase/interfaces/story.go (1 hunks)
  • server/pkg/storytelling/story.go (3 hunks)
  • server/pkg/storytelling/story_bulider.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (18)
  • server/internal/usecase/interfaces/story.go
  • server/internal/usecase/interactor/storytelling_test.go
  • server/e2e/gql_asset_test.go
  • server/e2e/mock_test.go
  • server/internal/adapter/gql/gqlmodel/convert_storytelling.go
  • server/internal/adapter/gql/resolver_mutation_storytelling.go
  • server/internal/app/app.go
  • server/internal/infrastructure/mongo/mongodoc/storytelling.go
  • server/pkg/storytelling/story_bulider.go
  • server/internal/app/auth_client.go
  • server/gql/storytelling.graphql
  • server/internal/usecase/interactor/storytelling.go
  • server/e2e/ping_test.go
  • server/pkg/storytelling/story.go
  • server/e2e/gql_project_test.go
  • server/internal/adapter/gql/gqlmodel/models_gen.go
  • server/internal/adapter/gql/generated.go
  • server/e2e/common.go
🔇 Additional comments (3)
server/e2e/gql_me_test.go (1)

8-8: Ensure test environment consistency.

Replacing StartServer with Server removes explicit test configuration (e.g., authentication or origin settings). Verify that the test environment remains aligned with the original expectations, especially if your tests rely on disabled authentication or other config parameters.

server/e2e/gql_storytelling_test.go (2)

501-514: Validate new fields in fetchSceneForStories.

The query now includes additional fields such as enableGa and trackingId. Ensure that all resolvers and front-end queries handle these fields properly, and consider cases where they might be absent or set to default values.


561-583: Confirm default behavior for new GA fields in updateStory.

Introducing enableGa and trackingId is beneficial, but confirm that your application handles default or null values without errors, especially if these fields are optional in GraphQL inputs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants