From c91e2e2e2994e28ec4caf032714ae84248faf718 Mon Sep 17 00:00:00 2001
From: Anush Kumar
Date: Wed, 22 Oct 2025 17:40:25 -0700
Subject: [PATCH 1/2] feat(ingest): enhance SelectTemplateStep with no results
message and improve Git repository fields
---
.../__tests__/common-git-info.test.tsx | 115 ++++++++
.../__tests__/constants-git-info.test.tsx | 135 +++++++++
.../__tests__/git-info-validation.test.tsx | 148 ++++++++++
.../RecipeForm/__tests__/lookml.test.tsx | 278 ++++++++++++++++++
.../source/builder/RecipeForm/common.tsx | 19 +-
.../source/builder/RecipeForm/constants.ts | 10 +-
.../source/builder/RecipeForm/lookml.tsx | 115 ++++++--
.../source/builder/SelectTemplateStep.tsx | 37 ++-
8 files changed, 812 insertions(+), 45 deletions(-)
create mode 100644 datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/common-git-info.test.tsx
create mode 100644 datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/constants-git-info.test.tsx
create mode 100644 datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/git-info-validation.test.tsx
create mode 100644 datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/lookml.test.tsx
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/common-git-info.test.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/common-git-info.test.tsx
new file mode 100644
index 00000000000000..2ed63279af1b32
--- /dev/null
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/common-git-info.test.tsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { Form } from 'antd';
+
+import { GIT_INFO_REPO } from '@app/ingestV2/source/builder/RecipeForm/common';
+import { FieldType } from '@app/ingestV2/source/builder/RecipeForm/common';
+
+// Mock FormField component for testing
+const MockFormField = ({ field, removeMargin }) => {
+ const { name, label, tooltip, type, placeholder, rules, required } = field;
+
+ return (
+
+
{label}
+ {tooltip &&
{typeof tooltip === 'string' ? tooltip : 'React tooltip'}
}
+
+
+ );
+};
+
+describe('Common Git Info Fields', () => {
+ describe('GIT_INFO_REPO', () => {
+ it('should have correct field properties', () => {
+ expect(GIT_INFO_REPO.name).toBe('git_info.repo');
+ expect(GIT_INFO_REPO.label).toBe('Git Repository');
+ expect(GIT_INFO_REPO.type).toBe(FieldType.TEXT);
+ expect(GIT_INFO_REPO.fieldPath).toBe('source.config.git_info.repo');
+ expect(GIT_INFO_REPO.rules).toBeNull();
+ });
+
+ it('should not be required by default', () => {
+ expect(GIT_INFO_REPO.required).toBeUndefined();
+ });
+
+ it('should render tooltip with multi-platform examples', () => {
+ render(
+
+ );
+
+ const tooltip = screen.getByTestId('tooltip-git_info.repo');
+ expect(tooltip.textContent).toContain('React tooltip');
+ });
+
+ it('should have updated from deprecated github_info', () => {
+ expect(GIT_INFO_REPO.name).not.toContain('github_info');
+ expect(GIT_INFO_REPO.name).toContain('git_info');
+ expect(GIT_INFO_REPO.label).not.toBe('GitHub Repo');
+ expect(GIT_INFO_REPO.label).toBe('Git Repository');
+ });
+
+ it('should support multiple Git platforms in tooltip', () => {
+ // Test that the tooltip contains information about multiple platforms
+ const tooltip = GIT_INFO_REPO.tooltip;
+ expect(tooltip).toBeDefined();
+
+ // Since tooltip is a React component, we test its structure
+ expect(React.isValidElement(tooltip)).toBe(true);
+ });
+ });
+
+ describe('Field Configuration', () => {
+ it('should have correct field path structure', () => {
+ expect(GIT_INFO_REPO.fieldPath).toBe('source.config.git_info.repo');
+ expect(GIT_INFO_REPO.fieldPath).toMatch(/^source\.config\.git_info\./);
+ });
+
+ it('should be a text input field', () => {
+ expect(GIT_INFO_REPO.type).toBe(FieldType.TEXT);
+ });
+
+ it('should not have validation rules', () => {
+ expect(GIT_INFO_REPO.rules).toBeNull();
+ });
+ });
+
+ describe('Multi-Platform Support', () => {
+ it('should have tooltip that mentions multiple platforms', () => {
+ // The tooltip should be a React component that includes examples
+ // for different Git platforms
+ expect(GIT_INFO_REPO.tooltip).toBeDefined();
+ expect(React.isValidElement(GIT_INFO_REPO.tooltip)).toBe(true);
+ });
+
+ it('should support flexible repository URL formats', () => {
+ // The field should accept various repository URL formats
+ // This is tested by the fact that there are no strict validation rules
+ expect(GIT_INFO_REPO.rules).toBeNull();
+ });
+ });
+
+ describe('Backward Compatibility', () => {
+ it('should replace deprecated github_info field', () => {
+ // Ensure the field name has been updated from github_info to git_info
+ expect(GIT_INFO_REPO.name).toBe('git_info.repo');
+ expect(GIT_INFO_REPO.name).not.toBe('github_info.repo');
+ });
+
+ it('should have updated label from GitHub-specific to generic', () => {
+ expect(GIT_INFO_REPO.label).toBe('Git Repository');
+ expect(GIT_INFO_REPO.label).not.toBe('GitHub Repo');
+ });
+
+ it('should have updated field path', () => {
+ expect(GIT_INFO_REPO.fieldPath).toBe('source.config.git_info.repo');
+ expect(GIT_INFO_REPO.fieldPath).not.toBe('source.config.github_info.repo');
+ });
+ });
+});
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/constants-git-info.test.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/constants-git-info.test.tsx
new file mode 100644
index 00000000000000..84db00b7b4cca4
--- /dev/null
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/constants-git-info.test.tsx
@@ -0,0 +1,135 @@
+import { LOOKML_GIT_INFO_REPO } from '@app/ingestV2/source/builder/RecipeForm/lookml';
+import { GIT_INFO_REPO } from '@app/ingestV2/source/builder/RecipeForm/common';
+import { LOOKML } from '@app/ingestV2/source/builder/RecipeForm/lookml';
+import { SOURCE_CONFIGS } from '@app/ingestV2/source/builder/RecipeForm/constants';
+
+describe('Constants Git Info Integration', () => {
+ describe('Field Imports', () => {
+ it('should import LOOKML_GIT_INFO_REPO from lookml module', () => {
+ expect(LOOKML_GIT_INFO_REPO).toBeDefined();
+ expect(LOOKML_GIT_INFO_REPO.name).toBe('git_info.repo');
+ });
+
+ it('should import GIT_INFO_REPO from common module', () => {
+ expect(GIT_INFO_REPO).toBeDefined();
+ expect(GIT_INFO_REPO.name).toBe('git_info.repo');
+ });
+ });
+
+ describe('Source Configuration Integration', () => {
+ it('should include LOOKML_GIT_INFO_REPO in LOOKML source config', () => {
+ const lookmlConfig = SOURCE_CONFIGS[LOOKML];
+ expect(lookmlConfig).toBeDefined();
+ expect(lookmlConfig.fields).toContain(LOOKML_GIT_INFO_REPO);
+ });
+
+ it('should have correct field order in LOOKML config', () => {
+ const lookmlConfig = SOURCE_CONFIGS[LOOKML];
+ const fields = lookmlConfig.fields;
+
+ // LOOKML_GIT_INFO_REPO should be the first field
+ expect(fields[0]).toBe(LOOKML_GIT_INFO_REPO);
+ });
+
+ it('should not contain deprecated github_info references', () => {
+ const lookmlConfig = SOURCE_CONFIGS[LOOKML];
+ const fields = lookmlConfig.fields;
+
+ // Check that no fields have github_info in their name
+ const githubInfoFields = fields.filter(field =>
+ field.name && field.name.includes('github_info')
+ );
+ expect(githubInfoFields).toHaveLength(0);
+ });
+
+ it('should contain git_info references', () => {
+ const lookmlConfig = SOURCE_CONFIGS[LOOKML];
+ const fields = lookmlConfig.fields;
+
+ // Check that fields have git_info in their name
+ const gitInfoFields = fields.filter(field =>
+ field.name && field.name.includes('git_info')
+ );
+ expect(gitInfoFields.length).toBeGreaterThan(0);
+ });
+ });
+
+ describe('Field Consistency', () => {
+ it('should have consistent field paths for git_info', () => {
+ const lookmlConfig = SOURCE_CONFIGS[LOOKML];
+ const fields = lookmlConfig.fields;
+
+ const gitInfoFields = fields.filter(field =>
+ field.name && field.name.includes('git_info')
+ );
+
+ gitInfoFields.forEach(field => {
+ expect(field.fieldPath).toMatch(/^source\.config\.git_info\./);
+ });
+ });
+
+ it('should have proper field types for git_info fields', () => {
+ const lookmlConfig = SOURCE_CONFIGS[LOOKML];
+ const fields = lookmlConfig.fields;
+
+ const gitInfoFields = fields.filter(field =>
+ field.name && field.name.includes('git_info')
+ );
+
+ gitInfoFields.forEach(field => {
+ expect(field.type).toBeDefined();
+ expect(['TEXT', 'SECRET']).toContain(field.type);
+ });
+ });
+ });
+
+ describe('Migration from github_info', () => {
+ it('should not have any github_info field references', () => {
+ const lookmlConfig = SOURCE_CONFIGS[LOOKML];
+ const fields = lookmlConfig.fields;
+
+ // Ensure no fields reference the old github_info structure
+ const oldFieldPaths = fields.filter(field =>
+ field.fieldPath && field.fieldPath.includes('github_info')
+ );
+ expect(oldFieldPaths).toHaveLength(0);
+ });
+
+ it('should have updated field names from github_info to git_info', () => {
+ const lookmlConfig = SOURCE_CONFIGS[LOOKML];
+ const fields = lookmlConfig.fields;
+
+ const gitInfoFields = fields.filter(field =>
+ field.name && field.name.includes('git_info')
+ );
+
+ expect(gitInfoFields.length).toBeGreaterThan(0);
+
+ gitInfoFields.forEach(field => {
+ expect(field.name).toMatch(/^git_info\./);
+ expect(field.name).not.toMatch(/^github_info\./);
+ });
+ });
+ });
+
+ describe('Field Validation', () => {
+ it('should have required fields properly marked', () => {
+ const lookmlConfig = SOURCE_CONFIGS[LOOKML];
+ const fields = lookmlConfig.fields;
+
+ const requiredFields = fields.filter(field => field.required === true);
+ expect(requiredFields.length).toBeGreaterThan(0);
+ });
+
+ it('should have validation rules for required fields', () => {
+ const lookmlConfig = SOURCE_CONFIGS[LOOKML];
+ const fields = lookmlConfig.fields;
+
+ const fieldsWithRules = fields.filter(field =>
+ field.rules && field.rules.length > 0
+ );
+
+ expect(fieldsWithRules.length).toBeGreaterThan(0);
+ });
+ });
+});
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/git-info-validation.test.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/git-info-validation.test.tsx
new file mode 100644
index 00000000000000..2ba43d1a6444ad
--- /dev/null
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/git-info-validation.test.tsx
@@ -0,0 +1,148 @@
+import { LOOKML_GIT_INFO_REPO_SSH_LOCATOR } from '@app/ingestV2/source/builder/RecipeForm/lookml';
+
+describe('Git Info Validation Logic', () => {
+ describe('REPO_SSH_LOCATOR Validation', () => {
+ const createValidator = (repoValue) => {
+ return LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
+ if (fieldName === 'git_info.repo') return repoValue;
+ return undefined;
+ }
+ });
+ };
+
+ describe('GitHub Repository Detection', () => {
+ it('should not require SSH locator for GitHub short format', async () => {
+ const validator = createValidator('datahub-project/datahub');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+
+ it('should not require SSH locator for GitHub full URL', async () => {
+ const validator = createValidator('https://github.com/datahub-project/datahub');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+
+ it('should not require SSH locator for GitHub URL without protocol', async () => {
+ const validator = createValidator('github.com/datahub-project/datahub');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+
+ it('should not require SSH locator for GitHub URL with trailing slash', async () => {
+ const validator = createValidator('https://github.com/datahub-project/datahub/');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+ });
+
+ describe('GitLab Repository Detection', () => {
+ it('should not require SSH locator for GitLab full URL', async () => {
+ const validator = createValidator('https://gitlab.com/gitlab-org/gitlab');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+
+ it('should not require SSH locator for GitLab URL without protocol', async () => {
+ const validator = createValidator('gitlab.com/gitlab-org/gitlab');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+
+ it('should not require SSH locator for GitLab URL with trailing slash', async () => {
+ const validator = createValidator('https://gitlab.com/gitlab-org/gitlab/');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+ });
+
+ describe('Other Git Platforms', () => {
+ it('should require SSH locator for Bitbucket', async () => {
+ const validator = createValidator('https://bitbucket.org/org/repo');
+ await expect(validator.validator({}, '')).rejects.toThrow(
+ 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab'
+ );
+ });
+
+ it('should require SSH locator for custom Git server', async () => {
+ const validator = createValidator('https://custom-git.com/org/repo');
+ await expect(validator.validator({}, '')).rejects.toThrow(
+ 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab'
+ );
+ });
+
+ it('should require SSH locator for SSH URL format', async () => {
+ const validator = createValidator('git@custom-server.com:org/repo');
+ await expect(validator.validator({}, '')).rejects.toThrow(
+ 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab'
+ );
+ });
+
+ it('should not require SSH locator when SSH locator is provided', async () => {
+ const validator = createValidator('https://custom-git.com/org/repo');
+ await expect(validator.validator({}, 'git@custom-git.com:org/repo.git')).resolves.toBeUndefined();
+ });
+ });
+
+ describe('Edge Cases', () => {
+ it('should not require SSH locator when repo is undefined', async () => {
+ const validator = createValidator(undefined);
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+
+ it('should not require SSH locator when repo is null', async () => {
+ const validator = createValidator(null);
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+
+ it('should not require SSH locator when repo is empty string', async () => {
+ const validator = createValidator('');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+
+ it('should handle case-insensitive GitHub detection', async () => {
+ const validator = createValidator('https://GITHUB.COM/datahub-project/datahub');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+
+ it('should handle case-insensitive GitLab detection', async () => {
+ const validator = createValidator('https://GITLAB.COM/gitlab-org/gitlab');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+ });
+
+ describe('Complex Repository URLs', () => {
+ it('should handle GitHub URLs with additional path segments', async () => {
+ const validator = createValidator('https://github.com/datahub-project/datahub/tree/main/src');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+
+ it('should handle GitLab URLs with additional path segments', async () => {
+ const validator = createValidator('https://gitlab.com/gitlab-org/gitlab/-/tree/main/src');
+ await expect(validator.validator({}, '')).resolves.toBeUndefined();
+ });
+
+ it('should require SSH locator for non-GitHub/GitLab URLs with similar patterns', async () => {
+ const validator = createValidator('https://github-enterprise.company.com/org/repo');
+ await expect(validator.validator({}, '')).rejects.toThrow(
+ 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab'
+ );
+ });
+ });
+ });
+
+ describe('Validation Error Messages', () => {
+ it('should provide clear error message for missing SSH locator', async () => {
+ const validator = createValidator('https://custom-git.com/org/repo');
+ try {
+ await validator.validator({}, '');
+ } catch (error) {
+ expect(error.message).toBe('Repository SSH Locator is required for Git platforms other than GitHub and GitLab');
+ }
+ });
+ });
+
+ // Helper function for creating validators
+ function createValidator(repoValue) {
+ return LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
+ if (fieldName === 'git_info.repo') return repoValue;
+ return undefined;
+ }
+ });
+ }
+});
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/lookml.test.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/lookml.test.tsx
new file mode 100644
index 00000000000000..5b27791b94d9db
--- /dev/null
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/lookml.test.tsx
@@ -0,0 +1,278 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { Form } from 'antd';
+
+import {
+ LOOKML_GIT_INFO_REPO,
+ LOOKML_GIT_INFO_DEPLOY_KEY,
+ LOOKML_GIT_INFO_REPO_SSH_LOCATOR,
+ LOOKML_BASE_URL,
+ LOOKML_CLIENT_ID,
+ LOOKML_CLIENT_SECRET,
+ PROJECT_NAME,
+ PARSE_TABLE_NAMES_FROM_SQL,
+ CONNECTION_TO_PLATFORM_MAP,
+} from '@app/ingestV2/source/builder/RecipeForm/lookml';
+import { FieldType } from '@app/ingestV2/source/builder/RecipeForm/common';
+
+// Mock FormField component for testing
+const MockFormField = ({ field, removeMargin }) => {
+ const { name, label, tooltip, type, placeholder, rules, required } = field;
+
+ return (
+
+
{label}
+ {tooltip &&
{typeof tooltip === 'string' ? tooltip : 'React tooltip'}
}
+
+
+ );
+};
+
+describe('LookML Git Info Fields', () => {
+ describe('LOOKML_GIT_INFO_REPO', () => {
+ it('should have correct field properties', () => {
+ expect(LOOKML_GIT_INFO_REPO.name).toBe('git_info.repo');
+ expect(LOOKML_GIT_INFO_REPO.label).toBe('Git Repository');
+ expect(LOOKML_GIT_INFO_REPO.type).toBe(FieldType.TEXT);
+ expect(LOOKML_GIT_INFO_REPO.fieldPath).toBe('source.config.git_info.repo');
+ expect(LOOKML_GIT_INFO_REPO.required).toBe(true);
+ expect(LOOKML_GIT_INFO_REPO.placeholder).toBe('datahub-project/datahub or https://github.com/datahub-project/datahub');
+ });
+
+ it('should have validation rules', () => {
+ expect(LOOKML_GIT_INFO_REPO.rules).toHaveLength(1);
+ expect(LOOKML_GIT_INFO_REPO.rules![0].required).toBe(true);
+ expect(LOOKML_GIT_INFO_REPO.rules![0].message).toBe('Git Repository is required');
+ });
+
+ it('should render tooltip with multi-platform support', () => {
+ render(
+
+ );
+
+ const tooltip = screen.getByTestId('tooltip-git_info.repo');
+ expect(tooltip.textContent).toContain('React tooltip');
+ });
+ });
+
+ describe('DEPLOY_KEY', () => {
+ it('should have correct field properties', () => {
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.name).toBe('git_info.deploy_key');
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.label).toBe('Git Deploy Key');
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.type).toBe(FieldType.SECRET);
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.fieldPath).toBe('source.config.git_info.deploy_key');
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.required).toBe(true);
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.placeholder).toBe('-----BEGIN OPENSSH PRIVATE KEY-----\n...');
+ });
+
+ it('should have validation rules', () => {
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.rules).toHaveLength(1);
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.rules![0].required).toBe(true);
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.rules![0].message).toBe('Git Deploy Key is required');
+ });
+
+ it('should have setValueOnRecipeOverride function', () => {
+ expect(typeof LOOKML_GIT_INFO_DEPLOY_KEY.setValueOnRecipeOverride).toBe('function');
+
+ const recipe = { source: { config: {} } };
+ const result = LOOKML_GIT_INFO_DEPLOY_KEY.setValueOnRecipeOverride!(recipe, 'test-key');
+ expect(result.source.config.git_info.deploy_key).toBe('test-key\n');
+ });
+
+ it('should render tooltip with multi-platform links', () => {
+ render(
+
+ );
+
+ const tooltip = screen.getByTestId('tooltip-git_info.deploy_key');
+ expect(tooltip.textContent).toContain('React tooltip');
+ });
+ });
+
+ describe('REPO_SSH_LOCATOR', () => {
+ it('should have correct field properties', () => {
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.name).toBe('git_info.repo_ssh_locator');
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.label).toBe('Repository SSH Locator');
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.type).toBe(FieldType.TEXT);
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.fieldPath).toBe('source.config.git_info.repo_ssh_locator');
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.placeholder).toBe('git@your-git-server.com:org/repo.git');
+ });
+
+ it('should have conditional validation rules', () => {
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules).toHaveLength(1);
+ expect(typeof LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules![0]).toBe('function');
+ });
+
+ it('should validate GitHub repos do not require SSH locator', async () => {
+ const validator = LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
+ if (fieldName === 'git_info.repo') return 'datahub-project/datahub';
+ return undefined;
+ }
+ });
+
+ const result = await validator.validator({}, '');
+ expect(result).toBeUndefined(); // Should not throw error
+ });
+
+ it('should validate GitLab repos do not require SSH locator', async () => {
+ const validator = LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
+ if (fieldName === 'git_info.repo') return 'https://gitlab.com/gitlab-org/gitlab';
+ return undefined;
+ }
+ });
+
+ const result = await validator.validator({}, '');
+ expect(result).toBeUndefined(); // Should not throw error
+ });
+
+ it('should require SSH locator for non-GitHub/GitLab repos', async () => {
+ const validator = LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
+ if (fieldName === 'git_info.repo') return 'https://custom-git.com/org/repo';
+ return undefined;
+ }
+ });
+
+ try {
+ await validator.validator({}, '');
+ expect(true).toBe(false); // Should not reach here
+ } catch (error: any) {
+ expect(error.message).toBe('Repository SSH Locator is required for Git platforms other than GitHub and GitLab');
+ }
+ });
+
+ it('should not require SSH locator when repo is not provided', async () => {
+ const validator = LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
+ if (fieldName === 'git_info.repo') return undefined;
+ return undefined;
+ }
+ });
+
+ const result = await validator.validator({}, '');
+ expect(result).toBeUndefined(); // Should not throw error
+ });
+
+ it('should render tooltip with examples', () => {
+ render(
+
+ );
+
+ const tooltip = screen.getByTestId('tooltip-git_info.repo_ssh_locator');
+ expect(tooltip.textContent).toContain('React tooltip');
+ });
+ });
+
+ describe('Field Integration', () => {
+ it('should have consistent field paths for git_info', () => {
+ expect(LOOKML_GIT_INFO_REPO.fieldPath).toBe('source.config.git_info.repo');
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.fieldPath).toBe('source.config.git_info.deploy_key');
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.fieldPath).toBe('source.config.git_info.repo_ssh_locator');
+ });
+
+ it('should have proper field types', () => {
+ expect(LOOKML_GIT_INFO_REPO.type).toBe(FieldType.TEXT);
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.type).toBe(FieldType.SECRET);
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.type).toBe(FieldType.TEXT);
+ });
+
+ it('should have required fields marked correctly', () => {
+ expect(LOOKML_GIT_INFO_REPO.required).toBe(true);
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.required).toBe(true);
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.required).toBeUndefined(); // Conditional requirement
+ });
+ });
+
+ describe('Backward Compatibility', () => {
+ it('should use git_info instead of deprecated github_info', () => {
+ expect(LOOKML_GIT_INFO_REPO.name).not.toContain('github_info');
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.name).not.toContain('github_info');
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.name).not.toContain('github_info');
+
+ expect(LOOKML_GIT_INFO_REPO.name).toContain('git_info');
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.name).toContain('git_info');
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.name).toContain('git_info');
+ });
+
+ it('should have updated labels from GitHub-specific to Git-generic', () => {
+ expect(LOOKML_GIT_INFO_REPO.label).toBe('Git Repository');
+ expect(LOOKML_GIT_INFO_DEPLOY_KEY.label).toBe('Git Deploy Key');
+ expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.label).toBe('Repository SSH Locator');
+ });
+ });
+
+ describe('Multi-Platform Support', () => {
+ it('should support GitHub repositories', () => {
+ const githubRepos = [
+ 'datahub-project/datahub',
+ 'https://github.com/datahub-project/datahub',
+ 'github.com/datahub-project/datahub'
+ ];
+
+ githubRepos.forEach(repo => {
+ const validator = LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
+ if (fieldName === 'git_info.repo') return repo;
+ return undefined;
+ }
+ });
+
+ expect(async () => {
+ await validator.validator({}, '');
+ }).not.toThrow();
+ });
+ });
+
+ it('should support GitLab repositories', () => {
+ const gitlabRepos = [
+ 'https://gitlab.com/gitlab-org/gitlab',
+ 'gitlab.com/gitlab-org/gitlab'
+ ];
+
+ gitlabRepos.forEach(repo => {
+ const validator = LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
+ if (fieldName === 'git_info.repo') return repo;
+ return undefined;
+ }
+ });
+
+ expect(async () => {
+ await validator.validator({}, '');
+ }).not.toThrow();
+ });
+ });
+
+ it('should require SSH locator for other Git platforms', async () => {
+ const otherPlatformRepos = [
+ 'https://bitbucket.org/org/repo',
+ 'https://custom-git.com/org/repo',
+ 'https://git.company.com/org/repo'
+ ];
+
+ for (const repo of otherPlatformRepos) {
+ const validator = LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
+ if (fieldName === 'git_info.repo') return repo;
+ return undefined;
+ }
+ });
+
+ await expect(validator.validator({}, '')).rejects.toThrow('Repository SSH Locator is required for Git platforms other than GitHub and GitLab');
+ }
+ });
+ });
+});
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/common.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/common.tsx
index 3a8a19927401e1..46b1e174c25462 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/common.tsx
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/common.tsx
@@ -411,22 +411,23 @@ export const INCLUDE_VIEWS: RecipeField = {
rules: null,
};
-export const GITHUB_INFO_REPO: RecipeField = {
- name: 'github_info.repo',
- label: 'GitHub Repo',
+export const GIT_INFO_REPO: RecipeField = {
+ name: 'git_info.repo',
+ label: 'Git Repository',
tooltip: (
- Name of your github repo. e.g. repo for{' '}
-
- https://github.com/datahub-project/datahub
- {' '}
- is datahub-project/datahub.
+ URL or name of your Git repository. Supports GitHub, GitLab, and other Git platforms. Examples:
+
+ GitHub: datahub-project/datahub or https://github.com/datahub-project/datahub
+ GitLab: https://gitlab.com/gitlab-org/gitlab
+ Other platforms: https://your-git-server.com/org/repo
+
),
type: FieldType.TEXT,
- fieldPath: 'source.config.github_info.repo',
+ fieldPath: 'source.config.git_info.repo',
rules: null,
};
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/constants.ts b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/constants.ts
index 1197ec6b4e00fa..d0fb2033c10b1b 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/constants.ts
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/constants.ts
@@ -101,12 +101,13 @@ import {
} from '@app/ingestV2/source/builder/RecipeForm/looker';
import {
CONNECTION_TO_PLATFORM_MAP,
- DEPLOY_KEY,
+ LOOKML_GIT_INFO_DEPLOY_KEY,
+ LOOKML_GIT_INFO_REPO_SSH_LOCATOR,
LOOKML,
LOOKML_BASE_URL,
LOOKML_CLIENT_ID,
LOOKML_CLIENT_SECRET,
- LOOKML_GITHUB_INFO_REPO,
+ LOOKML_GIT_INFO_REPO,
PARSE_TABLE_NAMES_FROM_SQL,
PROJECT_NAME,
} from '@app/ingestV2/source/builder/RecipeForm/lookml';
@@ -356,8 +357,9 @@ export const RECIPE_FIELDS: RecipeFields = {
},
[LOOKML]: {
fields: [
- LOOKML_GITHUB_INFO_REPO,
- DEPLOY_KEY,
+ LOOKML_GIT_INFO_REPO,
+ LOOKML_GIT_INFO_REPO_SSH_LOCATOR,
+ LOOKML_GIT_INFO_DEPLOY_KEY,
PROJECT_NAME,
LOOKML_BASE_URL,
LOOKML_CLIENT_ID,
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/lookml.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/lookml.tsx
index 7e1d0deb7f1d22..cc9835183182ed 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/lookml.tsx
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/lookml.tsx
@@ -5,42 +5,66 @@ import { FieldType, RecipeField, setFieldValueOnRecipe } from '@app/ingestV2/sou
export const LOOKML = 'lookml';
-export const LOOKML_GITHUB_INFO_REPO: RecipeField = {
- name: 'github_info.repo',
- label: 'GitHub Repo',
- tooltip: 'The name of the GitHub repository where your LookML is defined.',
+export const LOOKML_GIT_INFO_REPO: RecipeField = {
+ name: 'git_info.repo',
+ label: 'Git Repository',
+ tooltip: (
+ <>
+
+ Name of your GitHub repository or the URL of your Git repository. Supports GitHub, GitLab, and other Git platforms. Examples:
+
+ GitHub: datahub-project/datahub or https://github.com/datahub-project/datahub
+ GitLab: https://gitlab.com/gitlab-org/gitlab
+ Other platforms: https://your-git-server.com/org/repo (Repository SSH Locator is required)
+
+
+ >
+ ),
type: FieldType.TEXT,
- fieldPath: 'source.config.github_info.repo',
- placeholder: 'datahub-project/datahub',
- rules: [{ required: true, message: 'Github Repo is required' }],
+ fieldPath: 'source.config.git_info.repo',
+ placeholder: 'datahub-project/datahub or https://github.com/datahub-project/datahub',
+ rules: [{ required: true, message: 'Git Repository is required' }],
required: true,
};
-const deployKeyFieldPath = 'source.config.github_info.deploy_key';
-export const DEPLOY_KEY: RecipeField = {
- name: 'github_info.deploy_key',
- label: 'GitHub Deploy Key',
+const deployKeyFieldPath = 'source.config.git_info.deploy_key';
+export const LOOKML_GIT_INFO_DEPLOY_KEY: RecipeField = {
+ name: 'git_info.deploy_key',
+ label: 'Git Deploy Key',
tooltip: (
<>
- An SSH private key that has been provisioned for read access on the GitHub repository where the LookML is
+ An SSH private key that has been provisioned for read access on the Git repository where the LookML is
defined.
- Learn how to generate an SSH for your GitHub repository{' '}
-
- here
-
- .
+ Learn how to generate SSH keys for your Git platform:
+
+
+
+ GitHub
+
+
+
+
+ GitLab
+
+
+ Other Git platforms: Check your platform's documentation for SSH key setup
+
>
),
- type: FieldType.TEXTAREA,
- fieldPath: 'source.config.github_info.deploy_key',
+ type: FieldType.SECRET,
+ fieldPath: 'source.config.git_info.deploy_key',
placeholder: '-----BEGIN OPENSSH PRIVATE KEY-----\n...',
- rules: [{ required: true, message: 'Github Deploy Key is required' }],
+ rules: [{ required: true, message: 'Git Deploy Key is required' }],
setValueOnRecipeOverride: (recipe: any, value: string) => {
const valueWithNewLine = `${value}\n`;
return setFieldValueOnRecipe(recipe, valueWithNewLine, deployKeyFieldPath);
@@ -48,6 +72,49 @@ export const DEPLOY_KEY: RecipeField = {
required: true,
};
+export const LOOKML_GIT_INFO_REPO_SSH_LOCATOR: RecipeField = {
+ name: 'git_info.repo_ssh_locator',
+ label: 'Repository SSH Locator',
+ tooltip: (
+ <>
+ The SSH URL to clone the repository. Required for Git platforms other than GitHub and GitLab.
+
+
Examples:
+
+ GitHub: git@github.com:datahub-project/datahub.git
+ GitLab: git@gitlab.com:gitlab-org/gitlab.git
+ Other platforms: git@your-git-server.com:org/repo.git
+
+
+ >
+ ),
+ type: FieldType.TEXT,
+ fieldPath: 'source.config.git_info.repo_ssh_locator',
+ placeholder: 'git@your-git-server.com:org/repo.git',
+ rules: [
+ ({ getFieldValue }) => ({
+ validator(_, value) {
+ const repo = getFieldValue('git_info.repo');
+ if (!repo) return Promise.resolve();
+
+ // Check if it's GitHub or GitLab (these are auto-inferred)
+ const isGitHub = repo.toLowerCase().includes('github.com') ||
+ (!repo.includes('://') && repo.split('/').length === 2) ||
+ repo.startsWith('git@github.com:');
+ const isGitLab = repo.toLowerCase().includes('gitlab.com') ||
+ repo.startsWith('git@gitlab.com:');
+
+ if (!isGitHub && !isGitLab && !value) {
+ return Promise.reject(
+ new Error('Repository SSH Locator is required for Git platforms other than GitHub and GitLab')
+ );
+ }
+ return Promise.resolve();
+ },
+ }),
+ ],
+};
+
function validateApiSection(getFieldValue, fieldName) {
return {
validator(_, value) {
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/SelectTemplateStep.tsx b/datahub-web-react/src/app/ingestV2/source/builder/SelectTemplateStep.tsx
index 4cbe195b48a735..3de7f5482fdfc9 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/SelectTemplateStep.tsx
+++ b/datahub-web-react/src/app/ingestV2/source/builder/SelectTemplateStep.tsx
@@ -1,6 +1,6 @@
import { FormOutlined, SearchOutlined } from '@ant-design/icons';
import { Input } from 'antd';
-import React, { useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { ANTD_GRAY } from '@app/entity/shared/constants';
@@ -54,6 +54,17 @@ const PlatformListContainer = styled.div`
padding-right: 12px;
`;
+const NoResultsMessage = styled.div`
+ grid-column: 1 / -1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 40px 20px;
+ color: #666;
+ font-size: 16px;
+ text-align: center;
+`;
+
interface SourceOptionProps {
source: SourceConfig;
onClick: () => void;
@@ -86,12 +97,18 @@ export const SelectTemplateStep = ({
state,
updateState,
goTo,
- cancel,
ingestionSources,
setSelectedSourceType,
}: StepProps) => {
const [searchFilter, setSearchFilter] = useState('');
+ // Callback ref that focuses immediately when the element is attached
+ const searchInputCallbackRef = (node: any) => {
+ if (node) {
+ node.focus();
+ }
+ };
+
const onSelectTemplate = (type: string) => {
const newState: SourceBuilderState = {
...state,
@@ -126,6 +143,7 @@ export const SelectTemplateStep = ({
setSearchFilter(e.target.value)}
@@ -134,14 +152,17 @@ export const SelectTemplateStep = ({
/>
- {filteredSources.map((source) => (
- onSelectTemplate(source.name)} />
- ))}
+ {filteredSources.length > 0 ? (
+ filteredSources.map((source) => (
+ onSelectTemplate(source.name)} />
+ ))
+ ) : (
+
+ Data Source with name "{searchFilter}" not found.
+
+ )}
-
- Cancel
-
);
};
From a66ad534c90d8dcb65719c2d0c927269f24c2e11 Mon Sep 17 00:00:00 2001
From: Anush Kumar
Date: Thu, 23 Oct 2025 15:11:20 -0700
Subject: [PATCH 2/2] Lint fixes
---
.../__tests__/common-git-info.test.tsx | 30 ++---
.../__tests__/constants-git-info.test.tsx | 107 ++++++++----------
.../__tests__/git-info-validation.test.tsx | 42 +++----
.../RecipeForm/__tests__/lookml.test.tsx | 100 ++++++++--------
.../source/builder/RecipeForm/common.tsx | 4 +-
.../source/builder/RecipeForm/constants.ts | 4 +-
.../source/builder/RecipeForm/lookml.tsx | 27 ++---
.../source/builder/SelectTemplateStep.tsx | 13 ++-
8 files changed, 152 insertions(+), 175 deletions(-)
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/common-git-info.test.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/common-git-info.test.tsx
index 2ed63279af1b32..41f554c400b164 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/common-git-info.test.tsx
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/common-git-info.test.tsx
@@ -1,20 +1,22 @@
-import React from 'react';
import { render, screen } from '@testing-library/react';
import { Form } from 'antd';
+import React from 'react';
-import { GIT_INFO_REPO } from '@app/ingestV2/source/builder/RecipeForm/common';
-import { FieldType } from '@app/ingestV2/source/builder/RecipeForm/common';
+import { FieldType, GIT_INFO_REPO } from '@app/ingestV2/source/builder/RecipeForm/common';
// Mock FormField component for testing
-const MockFormField = ({ field, removeMargin }) => {
+const MockFormField = ({ field, removeMargin: _removeMargin }: { field: any; removeMargin: boolean }) => {
const { name, label, tooltip, type, placeholder, rules, required } = field;
-
+
return (
-
{label}
- {tooltip &&
{typeof tooltip === 'string' ? tooltip : 'React tooltip'}
}
-
{label}
+ {tooltip && (
+
{typeof tooltip === 'string' ? tooltip : 'React tooltip'}
+ )}
+
{
it('should render tooltip with multi-platform examples', () => {
render(
+
,
+ ,
);
-
+
const tooltip = screen.getByTestId('tooltip-git_info.repo');
expect(tooltip.textContent).toContain('React tooltip');
});
@@ -57,9 +59,9 @@ describe('Common Git Info Fields', () => {
it('should support multiple Git platforms in tooltip', () => {
// Test that the tooltip contains information about multiple platforms
- const tooltip = GIT_INFO_REPO.tooltip;
+ const { tooltip } = GIT_INFO_REPO;
expect(tooltip).toBeDefined();
-
+
// Since tooltip is a React component, we test its structure
expect(React.isValidElement(tooltip)).toBe(true);
});
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/constants-git-info.test.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/constants-git-info.test.tsx
index 84db00b7b4cca4..70b4f71d3e5cdd 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/constants-git-info.test.tsx
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/constants-git-info.test.tsx
@@ -1,7 +1,6 @@
-import { LOOKML_GIT_INFO_REPO } from '@app/ingestV2/source/builder/RecipeForm/lookml';
import { GIT_INFO_REPO } from '@app/ingestV2/source/builder/RecipeForm/common';
-import { LOOKML } from '@app/ingestV2/source/builder/RecipeForm/lookml';
-import { SOURCE_CONFIGS } from '@app/ingestV2/source/builder/RecipeForm/constants';
+import { RECIPE_FIELDS } from '@app/ingestV2/source/builder/RecipeForm/constants';
+import { LOOKML, LOOKML_GIT_INFO_REPO } from '@app/ingestV2/source/builder/RecipeForm/lookml';
describe('Constants Git Info Integration', () => {
describe('Field Imports', () => {
@@ -18,65 +17,57 @@ describe('Constants Git Info Integration', () => {
describe('Source Configuration Integration', () => {
it('should include LOOKML_GIT_INFO_REPO in LOOKML source config', () => {
- const lookmlConfig = SOURCE_CONFIGS[LOOKML];
+ const lookmlConfig = RECIPE_FIELDS[LOOKML];
expect(lookmlConfig).toBeDefined();
expect(lookmlConfig.fields).toContain(LOOKML_GIT_INFO_REPO);
});
it('should have correct field order in LOOKML config', () => {
- const lookmlConfig = SOURCE_CONFIGS[LOOKML];
- const fields = lookmlConfig.fields;
-
+ const lookmlConfig = RECIPE_FIELDS[LOOKML];
+ const { fields } = lookmlConfig;
+
// LOOKML_GIT_INFO_REPO should be the first field
expect(fields[0]).toBe(LOOKML_GIT_INFO_REPO);
});
it('should not contain deprecated github_info references', () => {
- const lookmlConfig = SOURCE_CONFIGS[LOOKML];
- const fields = lookmlConfig.fields;
-
+ const lookmlConfig = RECIPE_FIELDS[LOOKML];
+ const { fields } = lookmlConfig;
+
// Check that no fields have github_info in their name
- const githubInfoFields = fields.filter(field =>
- field.name && field.name.includes('github_info')
- );
+ const githubInfoFields = fields.filter((field) => field.name && field.name.includes('github_info'));
expect(githubInfoFields).toHaveLength(0);
});
it('should contain git_info references', () => {
- const lookmlConfig = SOURCE_CONFIGS[LOOKML];
- const fields = lookmlConfig.fields;
-
+ const lookmlConfig = RECIPE_FIELDS[LOOKML];
+ const { fields } = lookmlConfig;
+
// Check that fields have git_info in their name
- const gitInfoFields = fields.filter(field =>
- field.name && field.name.includes('git_info')
- );
+ const gitInfoFields = fields.filter((field) => field.name && field.name.includes('git_info'));
expect(gitInfoFields.length).toBeGreaterThan(0);
});
});
describe('Field Consistency', () => {
it('should have consistent field paths for git_info', () => {
- const lookmlConfig = SOURCE_CONFIGS[LOOKML];
- const fields = lookmlConfig.fields;
-
- const gitInfoFields = fields.filter(field =>
- field.name && field.name.includes('git_info')
- );
-
- gitInfoFields.forEach(field => {
+ const lookmlConfig = RECIPE_FIELDS[LOOKML];
+ const { fields } = lookmlConfig;
+
+ const gitInfoFields = fields.filter((field) => field.name && field.name.includes('git_info'));
+
+ gitInfoFields.forEach((field) => {
expect(field.fieldPath).toMatch(/^source\.config\.git_info\./);
});
});
it('should have proper field types for git_info fields', () => {
- const lookmlConfig = SOURCE_CONFIGS[LOOKML];
- const fields = lookmlConfig.fields;
-
- const gitInfoFields = fields.filter(field =>
- field.name && field.name.includes('git_info')
- );
-
- gitInfoFields.forEach(field => {
+ const lookmlConfig = RECIPE_FIELDS[LOOKML];
+ const { fields } = lookmlConfig;
+
+ const gitInfoFields = fields.filter((field) => field.name && field.name.includes('git_info'));
+
+ gitInfoFields.forEach((field) => {
expect(field.type).toBeDefined();
expect(['TEXT', 'SECRET']).toContain(field.type);
});
@@ -85,27 +76,23 @@ describe('Constants Git Info Integration', () => {
describe('Migration from github_info', () => {
it('should not have any github_info field references', () => {
- const lookmlConfig = SOURCE_CONFIGS[LOOKML];
- const fields = lookmlConfig.fields;
-
+ const lookmlConfig = RECIPE_FIELDS[LOOKML];
+ const { fields } = lookmlConfig;
+
// Ensure no fields reference the old github_info structure
- const oldFieldPaths = fields.filter(field =>
- field.fieldPath && field.fieldPath.includes('github_info')
- );
+ const oldFieldPaths = fields.filter((field) => field.fieldPath && field.fieldPath.includes('github_info'));
expect(oldFieldPaths).toHaveLength(0);
});
it('should have updated field names from github_info to git_info', () => {
- const lookmlConfig = SOURCE_CONFIGS[LOOKML];
- const fields = lookmlConfig.fields;
-
- const gitInfoFields = fields.filter(field =>
- field.name && field.name.includes('git_info')
- );
-
+ const lookmlConfig = RECIPE_FIELDS[LOOKML];
+ const { fields } = lookmlConfig;
+
+ const gitInfoFields = fields.filter((field) => field.name && field.name.includes('git_info'));
+
expect(gitInfoFields.length).toBeGreaterThan(0);
-
- gitInfoFields.forEach(field => {
+
+ gitInfoFields.forEach((field) => {
expect(field.name).toMatch(/^git_info\./);
expect(field.name).not.toMatch(/^github_info\./);
});
@@ -114,21 +101,19 @@ describe('Constants Git Info Integration', () => {
describe('Field Validation', () => {
it('should have required fields properly marked', () => {
- const lookmlConfig = SOURCE_CONFIGS[LOOKML];
- const fields = lookmlConfig.fields;
-
- const requiredFields = fields.filter(field => field.required === true);
+ const lookmlConfig = RECIPE_FIELDS[LOOKML];
+ const { fields } = lookmlConfig;
+
+ const requiredFields = fields.filter((field) => field.required === true);
expect(requiredFields.length).toBeGreaterThan(0);
});
it('should have validation rules for required fields', () => {
- const lookmlConfig = SOURCE_CONFIGS[LOOKML];
- const fields = lookmlConfig.fields;
-
- const fieldsWithRules = fields.filter(field =>
- field.rules && field.rules.length > 0
- );
-
+ const lookmlConfig = RECIPE_FIELDS[LOOKML];
+ const { fields } = lookmlConfig;
+
+ const fieldsWithRules = fields.filter((field) => field.rules && field.rules.length > 0);
+
expect(fieldsWithRules.length).toBeGreaterThan(0);
});
});
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/git-info-validation.test.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/git-info-validation.test.tsx
index 2ba43d1a6444ad..6444d368c7ae4c 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/git-info-validation.test.tsx
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/git-info-validation.test.tsx
@@ -1,16 +1,16 @@
import { LOOKML_GIT_INFO_REPO_SSH_LOCATOR } from '@app/ingestV2/source/builder/RecipeForm/lookml';
describe('Git Info Validation Logic', () => {
- describe('REPO_SSH_LOCATOR Validation', () => {
- const createValidator = (repoValue) => {
- return LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
- if (fieldName === 'git_info.repo') return repoValue;
- return undefined;
- }
- });
- };
+ const createValidator = (repoValue: string | undefined | null) => {
+ return LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
+ if (fieldName === 'git_info.repo') return repoValue;
+ return undefined;
+ },
+ });
+ };
+ describe('REPO_SSH_LOCATOR Validation', () => {
describe('GitHub Repository Detection', () => {
it('should not require SSH locator for GitHub short format', async () => {
const validator = createValidator('datahub-project/datahub');
@@ -54,21 +54,21 @@ describe('Git Info Validation Logic', () => {
it('should require SSH locator for Bitbucket', async () => {
const validator = createValidator('https://bitbucket.org/org/repo');
await expect(validator.validator({}, '')).rejects.toThrow(
- 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab'
+ 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab',
);
});
it('should require SSH locator for custom Git server', async () => {
const validator = createValidator('https://custom-git.com/org/repo');
await expect(validator.validator({}, '')).rejects.toThrow(
- 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab'
+ 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab',
);
});
it('should require SSH locator for SSH URL format', async () => {
const validator = createValidator('git@custom-server.com:org/repo');
await expect(validator.validator({}, '')).rejects.toThrow(
- 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab'
+ 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab',
);
});
@@ -119,7 +119,7 @@ describe('Git Info Validation Logic', () => {
it('should require SSH locator for non-GitHub/GitLab URLs with similar patterns', async () => {
const validator = createValidator('https://github-enterprise.company.com/org/repo');
await expect(validator.validator({}, '')).rejects.toThrow(
- 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab'
+ 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab',
);
});
});
@@ -130,19 +130,11 @@ describe('Git Info Validation Logic', () => {
const validator = createValidator('https://custom-git.com/org/repo');
try {
await validator.validator({}, '');
- } catch (error) {
- expect(error.message).toBe('Repository SSH Locator is required for Git platforms other than GitHub and GitLab');
+ } catch (error: any) {
+ expect(error.message).toBe(
+ 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab',
+ );
}
});
});
-
- // Helper function for creating validators
- function createValidator(repoValue) {
- return LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
- if (fieldName === 'git_info.repo') return repoValue;
- return undefined;
- }
- });
- }
});
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/lookml.test.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/lookml.test.tsx
index 5b27791b94d9db..e507d738bd5eb7 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/lookml.test.tsx
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/__tests__/lookml.test.tsx
@@ -1,30 +1,27 @@
-import React from 'react';
import { render, screen } from '@testing-library/react';
import { Form } from 'antd';
+import React from 'react';
+import { FieldType } from '@app/ingestV2/source/builder/RecipeForm/common';
import {
- LOOKML_GIT_INFO_REPO,
LOOKML_GIT_INFO_DEPLOY_KEY,
+ LOOKML_GIT_INFO_REPO,
LOOKML_GIT_INFO_REPO_SSH_LOCATOR,
- LOOKML_BASE_URL,
- LOOKML_CLIENT_ID,
- LOOKML_CLIENT_SECRET,
- PROJECT_NAME,
- PARSE_TABLE_NAMES_FROM_SQL,
- CONNECTION_TO_PLATFORM_MAP,
} from '@app/ingestV2/source/builder/RecipeForm/lookml';
-import { FieldType } from '@app/ingestV2/source/builder/RecipeForm/common';
// Mock FormField component for testing
-const MockFormField = ({ field, removeMargin }) => {
+const MockFormField = ({ field, removeMargin: _removeMargin }: { field: any; removeMargin: boolean }) => {
const { name, label, tooltip, type, placeholder, rules, required } = field;
-
+
return (
-
{label}
- {tooltip &&
{typeof tooltip === 'string' ? tooltip : 'React tooltip'}
}
-
{label}
+ {tooltip && (
+
{typeof tooltip === 'string' ? tooltip : 'React tooltip'}
+ )}
+
{
expect(LOOKML_GIT_INFO_REPO.type).toBe(FieldType.TEXT);
expect(LOOKML_GIT_INFO_REPO.fieldPath).toBe('source.config.git_info.repo');
expect(LOOKML_GIT_INFO_REPO.required).toBe(true);
- expect(LOOKML_GIT_INFO_REPO.placeholder).toBe('datahub-project/datahub or https://github.com/datahub-project/datahub');
+ expect(LOOKML_GIT_INFO_REPO.placeholder).toBe(
+ 'datahub-project/datahub or https://github.com/datahub-project/datahub',
+ );
});
it('should have validation rules', () => {
@@ -54,9 +53,9 @@ describe('LookML Git Info Fields', () => {
render(
+ ,
);
-
+
const tooltip = screen.getByTestId('tooltip-git_info.repo');
expect(tooltip.textContent).toContain('React tooltip');
});
@@ -80,7 +79,7 @@ describe('LookML Git Info Fields', () => {
it('should have setValueOnRecipeOverride function', () => {
expect(typeof LOOKML_GIT_INFO_DEPLOY_KEY.setValueOnRecipeOverride).toBe('function');
-
+
const recipe = { source: { config: {} } };
const result = LOOKML_GIT_INFO_DEPLOY_KEY.setValueOnRecipeOverride!(recipe, 'test-key');
expect(result.source.config.git_info.deploy_key).toBe('test-key\n');
@@ -90,9 +89,9 @@ describe('LookML Git Info Fields', () => {
render(
+ ,
);
-
+
const tooltip = screen.getByTestId('tooltip-git_info.deploy_key');
expect(tooltip.textContent).toContain('React tooltip');
});
@@ -117,7 +116,7 @@ describe('LookML Git Info Fields', () => {
getFieldValue: (fieldName) => {
if (fieldName === 'git_info.repo') return 'datahub-project/datahub';
return undefined;
- }
+ },
});
const result = await validator.validator({}, '');
@@ -129,7 +128,7 @@ describe('LookML Git Info Fields', () => {
getFieldValue: (fieldName) => {
if (fieldName === 'git_info.repo') return 'https://gitlab.com/gitlab-org/gitlab';
return undefined;
- }
+ },
});
const result = await validator.validator({}, '');
@@ -141,14 +140,16 @@ describe('LookML Git Info Fields', () => {
getFieldValue: (fieldName) => {
if (fieldName === 'git_info.repo') return 'https://custom-git.com/org/repo';
return undefined;
- }
+ },
});
try {
await validator.validator({}, '');
expect(true).toBe(false); // Should not reach here
} catch (error: any) {
- expect(error.message).toBe('Repository SSH Locator is required for Git platforms other than GitHub and GitLab');
+ expect(error.message).toBe(
+ 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab',
+ );
}
});
@@ -157,7 +158,7 @@ describe('LookML Git Info Fields', () => {
getFieldValue: (fieldName) => {
if (fieldName === 'git_info.repo') return undefined;
return undefined;
- }
+ },
});
const result = await validator.validator({}, '');
@@ -168,9 +169,9 @@ describe('LookML Git Info Fields', () => {
render(
+ ,
);
-
+
const tooltip = screen.getByTestId('tooltip-git_info.repo_ssh_locator');
expect(tooltip.textContent).toContain('React tooltip');
});
@@ -201,7 +202,7 @@ describe('LookML Git Info Fields', () => {
expect(LOOKML_GIT_INFO_REPO.name).not.toContain('github_info');
expect(LOOKML_GIT_INFO_DEPLOY_KEY.name).not.toContain('github_info');
expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.name).not.toContain('github_info');
-
+
expect(LOOKML_GIT_INFO_REPO.name).toContain('git_info');
expect(LOOKML_GIT_INFO_DEPLOY_KEY.name).toContain('git_info');
expect(LOOKML_GIT_INFO_REPO_SSH_LOCATOR.name).toContain('git_info');
@@ -219,15 +220,15 @@ describe('LookML Git Info Fields', () => {
const githubRepos = [
'datahub-project/datahub',
'https://github.com/datahub-project/datahub',
- 'github.com/datahub-project/datahub'
+ 'github.com/datahub-project/datahub',
];
- githubRepos.forEach(repo => {
+ githubRepos.forEach((repo) => {
const validator = LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
if (fieldName === 'git_info.repo') return repo;
return undefined;
- }
+ },
});
expect(async () => {
@@ -237,17 +238,14 @@ describe('LookML Git Info Fields', () => {
});
it('should support GitLab repositories', () => {
- const gitlabRepos = [
- 'https://gitlab.com/gitlab-org/gitlab',
- 'gitlab.com/gitlab-org/gitlab'
- ];
+ const gitlabRepos = ['https://gitlab.com/gitlab-org/gitlab', 'gitlab.com/gitlab-org/gitlab'];
- gitlabRepos.forEach(repo => {
+ gitlabRepos.forEach((repo) => {
const validator = LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
if (fieldName === 'git_info.repo') return repo;
return undefined;
- }
+ },
});
expect(async () => {
@@ -260,19 +258,23 @@ describe('LookML Git Info Fields', () => {
const otherPlatformRepos = [
'https://bitbucket.org/org/repo',
'https://custom-git.com/org/repo',
- 'https://git.company.com/org/repo'
+ 'https://git.company.com/org/repo',
];
- for (const repo of otherPlatformRepos) {
- const validator = LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
- if (fieldName === 'git_info.repo') return repo;
- return undefined;
- }
- });
-
- await expect(validator.validator({}, '')).rejects.toThrow('Repository SSH Locator is required for Git platforms other than GitHub and GitLab');
- }
+ await Promise.all(
+ otherPlatformRepos.map(async (repo) => {
+ const validator = LOOKML_GIT_INFO_REPO_SSH_LOCATOR.rules => {
+ if (fieldName === 'git_info.repo') return repo;
+ return undefined;
+ },
+ });
+
+ await expect(validator.validator({}, '')).rejects.toThrow(
+ 'Repository SSH Locator is required for Git platforms other than GitHub and GitLab',
+ );
+ }),
+ );
});
});
});
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/common.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/common.tsx
index 46b1e174c25462..d64858e6352a51 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/common.tsx
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/common.tsx
@@ -416,9 +416,7 @@ export const GIT_INFO_REPO: RecipeField = {
label: 'Git Repository',
tooltip: (
-
- URL or name of your Git repository. Supports GitHub, GitLab, and other Git platforms. Examples:
-
+
URL or name of your Git repository. Supports GitHub, GitLab, and other Git platforms. Examples:
GitHub: datahub-project/datahub or https://github.com/datahub-project/datahub
GitLab: https://gitlab.com/gitlab-org/gitlab
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/constants.ts b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/constants.ts
index d0fb2033c10b1b..b00e2908e2295f 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/constants.ts
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/constants.ts
@@ -101,13 +101,13 @@ import {
} from '@app/ingestV2/source/builder/RecipeForm/looker';
import {
CONNECTION_TO_PLATFORM_MAP,
- LOOKML_GIT_INFO_DEPLOY_KEY,
- LOOKML_GIT_INFO_REPO_SSH_LOCATOR,
LOOKML,
LOOKML_BASE_URL,
LOOKML_CLIENT_ID,
LOOKML_CLIENT_SECRET,
+ LOOKML_GIT_INFO_DEPLOY_KEY,
LOOKML_GIT_INFO_REPO,
+ LOOKML_GIT_INFO_REPO_SSH_LOCATOR,
PARSE_TABLE_NAMES_FROM_SQL,
PROJECT_NAME,
} from '@app/ingestV2/source/builder/RecipeForm/lookml';
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/lookml.tsx b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/lookml.tsx
index cc9835183182ed..33fbb473f2d948 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/lookml.tsx
+++ b/datahub-web-react/src/app/ingestV2/source/builder/RecipeForm/lookml.tsx
@@ -11,7 +11,8 @@ export const LOOKML_GIT_INFO_REPO: RecipeField = {
tooltip: (
<>
- Name of your GitHub repository or the URL of your Git repository. Supports GitHub, GitLab, and other Git platforms. Examples:
+ Name of your GitHub repository or the URL of your Git repository. Supports GitHub, GitLab, and other Git
+ platforms. Examples:
GitHub: datahub-project/datahub or https://github.com/datahub-project/datahub
GitLab: https://gitlab.com/gitlab-org/gitlab
@@ -48,15 +49,11 @@ export const LOOKML_GIT_INFO_DEPLOY_KEY: RecipeField = {
-
+
GitLab
- Other Git platforms: Check your platform's documentation for SSH key setup
+ Other Git platforms: Check your platform's documentation for SSH key setup
>
@@ -96,17 +93,17 @@ export const LOOKML_GIT_INFO_REPO_SSH_LOCATOR: RecipeField = {
validator(_, value) {
const repo = getFieldValue('git_info.repo');
if (!repo) return Promise.resolve();
-
+
// Check if it's GitHub or GitLab (these are auto-inferred)
- const isGitHub = repo.toLowerCase().includes('github.com') ||
- (!repo.includes('://') && repo.split('/').length === 2) ||
- repo.startsWith('git@github.com:');
- const isGitLab = repo.toLowerCase().includes('gitlab.com') ||
- repo.startsWith('git@gitlab.com:');
-
+ const isGitHub =
+ repo.toLowerCase().includes('github.com') ||
+ (!repo.includes('://') && repo.split('/').length === 2) ||
+ repo.startsWith('git@github.com:');
+ const isGitLab = repo.toLowerCase().includes('gitlab.com') || repo.startsWith('git@gitlab.com:');
+
if (!isGitHub && !isGitLab && !value) {
return Promise.reject(
- new Error('Repository SSH Locator is required for Git platforms other than GitHub and GitLab')
+ new Error('Repository SSH Locator is required for Git platforms other than GitHub and GitLab'),
);
}
return Promise.resolve();
diff --git a/datahub-web-react/src/app/ingestV2/source/builder/SelectTemplateStep.tsx b/datahub-web-react/src/app/ingestV2/source/builder/SelectTemplateStep.tsx
index 3de7f5482fdfc9..b280bef76e67a1 100644
--- a/datahub-web-react/src/app/ingestV2/source/builder/SelectTemplateStep.tsx
+++ b/datahub-web-react/src/app/ingestV2/source/builder/SelectTemplateStep.tsx
@@ -1,6 +1,6 @@
import { FormOutlined, SearchOutlined } from '@ant-design/icons';
import { Input } from 'antd';
-import React, { useEffect, useRef, useState } from 'react';
+import React, { useState } from 'react';
import styled from 'styled-components';
import { ANTD_GRAY } from '@app/entity/shared/constants';
@@ -9,7 +9,6 @@ import { CUSTOM } from '@app/ingestV2/source/builder/constants';
import { IngestionSourceBuilderStep } from '@app/ingestV2/source/builder/steps';
import { SourceBuilderState, SourceConfig, StepProps } from '@app/ingestV2/source/builder/types';
import useGetSourceLogoUrl from '@app/ingestV2/source/builder/useGetSourceLogoUrl';
-import { Button } from '@src/alchemy-components';
const Container = styled.div`
max-height: 82vh;
@@ -154,12 +153,14 @@ export const SelectTemplateStep = ({
{filteredSources.length > 0 ? (
filteredSources.map((source) => (
- onSelectTemplate(source.name)} />
+ onSelectTemplate(source.name)}
+ />
))
) : (
-
- Data Source with name "{searchFilter}" not found.
-
+ Data Source with name "{searchFilter}" not found.
)}