-
Notifications
You must be signed in to change notification settings - Fork 344
fix: support mid-migration metadata templates #4653
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -193,13 +193,20 @@ class Metadata extends File { | |||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * API URL for getting metadata template schema by template key | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * API URL for getting metadata template schema by template key. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * In SCOPED mode the path segment is the `enterprise` shorthand. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * In MIGRATION/FINAL mode the API requires the full scope value (e.g. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * `enterprise_123456`) or a namespace FQN, so callers should pass the | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * resolved scope/namespace when operating in those modes. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {string} templateKey - metadata template key | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {string} [scope] - scope or namespace FQN; defaults to the | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * `enterprise` shorthand for backward compatibility with SCOPED mode | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return {string} API url for getting template schema by template key | ||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| getMetadataTemplateSchemaUrl(templateKey: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return `${this.getMetadataTemplateUrl()}/enterprise/${templateKey}/schema`; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| getMetadataTemplateSchemaUrl(templateKey: string, scope?: string = METADATA_SCOPE_ENTERPRISE): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return `${this.getMetadataTemplateUrl()}/${scope}/${templateKey}/schema`; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -403,25 +410,27 @@ class Metadata extends File { | |||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Gets metadata template schema by template key | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Gets metadata template schema by template key. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * In MIGRATION/FINAL mode, pass the full scope (e.g. `enterprise_123456`) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * or namespace FQN as `scope` so the correct URL path segment is used. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Omitting `scope` falls back to the `enterprise` shorthand (SCOPED mode). | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {string} templateKey - template key | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {string} [scope] - scope or namespace FQN (defaults to `enterprise`) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return {Promise} Promise object of metadata template | ||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| async getSchemaByTemplateKey(templateKey: string): Promise<MetadataTemplateSchemaResponse> { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| async getSchemaByTemplateKey(templateKey: string, scope?: string): Promise<MetadataTemplateSchemaResponse> { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const cache: APICache = this.getCache(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const key = this.getMetadataTemplateSchemaCacheKey(templateKey); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Return cached value if it exists | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (cache.has(key)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return cache.get(key); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fetch from API if not cached | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const url = this.getMetadataTemplateSchemaUrl(templateKey); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const url = this.getMetadataTemplateSchemaUrl(templateKey, scope); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await this.xhr.get({ url }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Cache the response | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cache.set(key, response); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| return response; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -553,10 +562,21 @@ class Metadata extends File { | |||||||||||||||||||||||||||||||||||||||||||||||||
| const instanceId = instance.$id; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const templateKey = instance.$template; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const scope = instance.$scope; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const template = templates.find(t => t.templateKey === templateKey && t.scope === scope); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const namespace = instance.$namespace; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Primary match: by scope (SCOPED mode; also works for enterprise-scoped | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // instances in MIGRATION mode where $scope is still populated). | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let template = templates.find(t => t.templateKey === templateKey && t.scope === scope); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fallback match: by namespace for namespace-only instances in | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // MIGRATION/FINAL mode where $scope is absent. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!template && namespace) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| template = templates.find(t => t.templateKey === templateKey && t.namespace === namespace); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+565
to
+575
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Confirm how `scope` is derived in getTemplateForInstance (definition not shown)
rg -nP --type=js -C3 'getTemplateForInstance' src/api/Metadata.jsRepository: box/box-ui-elements Length of output: 1191 🏁 Script executed: sed -n '557,600p' src/api/Metadata.jsRepository: box/box-ui-elements Length of output: 2389 Primary scope match can short-circuit the namespace fallback when For namespace-only instances, Gate the primary match on a defined scope to ensure the namespace fallback runs correctly: Proposed fix- let template = templates.find(t => t.templateKey === templateKey && t.scope === scope);
+ let template = scope
+ ? templates.find(t => t.templateKey === templateKey && t.scope === scope)
+ : undefined;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Enterprise scopes are always enterprise_XXXXX | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!template && scope.startsWith(METADATA_SCOPE_ENTERPRISE)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Enterprise scopes are always enterprise_XXXXX; use optional chaining | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // to guard against namespace-only instances where $scope is undefined. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!template && scope?.startsWith(METADATA_SCOPE_ENTERPRISE)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Any missing template is likely from another enterprise (e.g. collaborated file); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Templates array has no pagination so we can assume cross-enterprise as it contains all templates. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const crossEnterpriseTemplates = await this.getTemplates(id, scope, instanceId, true); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Cache key must include
scopeto avoid cross-scope collisions.getMetadataTemplateSchemaCacheKeykeys solely ontemplateKey, but the schema URL now varies byscope. After the first fetch for a giventemplateKey, a subsequent call with a differentscope(e.g.enterprise_123456vs a namespace FQN) returns the wrong cached schema and never hits the scope-specific URL built on Line 431. This is exactly the MIGRATION/FINAL multi-scope scenario this PR targets.Proposed fix
🧰 Tools
🪛 Biome (2.5.0)
[error] 423-423: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.
(parse)
[error] 423-423: optional parameters are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.
(parse)
[error] 423-423: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.
(parse)
[error] 423-423: return type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.
(parse)
[error] 424-424: type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.
(parse)
🤖 Prompt for AI Agents