-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add setup wizard with CSV import and @wareflow/db package #10
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
Changes from all commits
08c71d4
b4feb78
e5f15c3
5b7130a
89e979a
38e653a
e27f8d6
f4adf92
9175dc6
c93e852
0cc3dce
22a4598
e3d9953
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 |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| name: PR Review | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, ready_for_review, reopened] | ||
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| review: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| steps: | ||
| - name: Checkout PR branch | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 1 | ||
|
|
||
| - name: Generate Marty token | ||
| id: marty-token | ||
| uses: actions/create-github-app-token@v2 | ||
| with: | ||
| app-id: ${{ secrets.MARTY_APP_ID }} | ||
| private-key: ${{ secrets.MARTY_APP_PRIVATE_KEY }} | ||
|
|
||
| - name: Run Marty PR Review | ||
| uses: nesalia-inc/marty-action@1.0.0 | ||
|
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. For security, third-party actions should be pinned to a specific commit SHA rather than a version tag. This prevents potential supply chain attacks if the version tag is redirected. Consider using |
||
| with: | ||
| github_token: ${{ steps.marty-token.outputs.token }} | ||
| prompt: | | ||
| REPO: ${{ github.repository }} | ||
| PR NUMBER: ${{ github.event.pull_request.number }} | ||
| AUTHOR: ${{ github.event.pull_request.user.login }} | ||
| TITLE: ${{ github.event.pull_request.title }} | ||
|
|
||
| Please review this pull request comprehensively: | ||
|
|
||
| ## Review Focus Areas | ||
|
|
||
| ### Code Quality | ||
| - Code follows best practices and design patterns | ||
| - Proper error handling and edge cases | ||
| - No code duplication (DRY principle) | ||
| - Clear and maintainable code structure | ||
|
|
||
| ### Security | ||
| - No hardcoded secrets or credentials | ||
| - Proper input validation and sanitization | ||
| - SQL injection and XSS vulnerabilities | ||
| - Authentication and authorization checks | ||
| - Sensitive data handling | ||
|
|
||
| ### Performance | ||
| - Potential performance bottlenecks | ||
| - Efficient database queries | ||
| - Proper caching strategies | ||
| - Resource cleanup and memory leaks | ||
|
|
||
| ### Testing | ||
| - Adequate test coverage for changes | ||
| - Edge cases covered | ||
| - Test quality and assertions | ||
|
|
||
| ### Documentation | ||
| - README updated if needed | ||
| - Inline comments for complex logic | ||
| - API documentation updated | ||
| - Breaking changes documented | ||
|
|
||
| ## Review Output Format | ||
|
|
||
| Use inline comments for specific code issues (highlight exact lines). | ||
| Use top-level PR comments for general observations and summary. | ||
|
|
||
| Structure your top-level comment as: | ||
| - **Summary**: Brief overview | ||
| - **Critical Issues**: Must-fix items | ||
| - **Recommendations**: Suggestions for improvement | ||
| - **Positive Notes**: Good practices observed | ||
|
|
||
| Note: The PR branch is already checked out in the current working directory. | ||
|
|
||
| Only post GitHub comments - don't submit review text as messages. | ||
|
|
||
| claude_args: | | ||
| --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" | ||
| --max-turns 100 | ||
|
|
||
| env: | ||
| ANTHROPIC_BASE_URL: https://api.minimax.io/anthropic | ||
| ANTHROPIC_AUTH_TOKEN: ${{ secrets.MINIMAX_API_KEY }} | ||
| ANTHROPIC_DEFAULT_SONNET_MODEL: MiniMax-M2.5 | ||
| ANTHROPIC_DEFAULT_HAIKU_MODEL: MiniMax-M2.5 | ||
| ANTHROPIC_DEFAULT_OPUS_MODEL: MiniMax-M2.5 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # Dependencies | ||
| node_modules/ | ||
| .pnpm-store/ | ||
|
|
||
| # Build outputs | ||
| dist/ | ||
| out/ | ||
| build/ | ||
|
|
||
| # IDE | ||
| .idea/ | ||
| .vscode/ | ||
| *.swp | ||
| *.swo | ||
|
|
||
| # OS | ||
| .DS_Store | ||
| Thumbs.db | ||
|
|
||
| # Logs | ||
| *.log | ||
| npm-debug.log* | ||
|
|
||
| # Environment | ||
| .env | ||
| .env.local | ||
| .env.*.local | ||
|
|
||
| # Testing | ||
| coverage/ | ||
|
|
||
| # Turbo | ||
| .turbo/ | ||
|
|
||
| # Misc | ||
| *.tsbuildinfo |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| import { defineConfig, externalizeDepsPlugin } from 'electron-vite' | ||
|
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. The |
||
| import { resolve } from 'path' | ||
| import tailwindcss from '@tailwindcss/vite' | ||
| import tsconfigPaths from 'vite-tsconfig-paths' | ||
|
|
||
| export default defineConfig({ | ||
| main: { | ||
|
|
@@ -23,13 +25,21 @@ export default defineConfig({ | |
| } | ||
| }, | ||
| renderer: { | ||
|
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. The 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. Security Issue: The desktop renderer is sharing the same |
||
| root: resolve(__dirname, '../web/dist'), | ||
| root: resolve(__dirname, '../web'), | ||
|
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. This is a positive change - pointing to the source directory instead of dist allows the desktop app to use the web app's Vite dev server with HMR. |
||
| plugins: [ | ||
| tailwindcss(), | ||
| tsconfigPaths({ projects: ['./tsconfig.json'] }) | ||
| ], | ||
| build: { | ||
| rollupOptions: { | ||
| input: { | ||
|
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. Issue: The desktop renderer configuration uses the web app's Recommendation: Consider creating a separate renderer entry point for the desktop app (e.g., |
||
| index: resolve(__dirname, '../web/dist/index.html') | ||
| index: resolve(__dirname, '../web/index.html') | ||
| } | ||
| } | ||
| }, | ||
| server: { | ||
| host: '127.0.0.1', | ||
|
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. Both apps are configured to use port 3000. When running |
||
| port: 3000 | ||
| } | ||
| } | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| import { useMemo } from 'react' | ||
| import { ArrowLeft, ArrowRight, RefreshCw, Columns2 } from 'lucide-react' | ||
| import { Button } from '../ui/button' | ||
| import { IMPORT_FIELDS, type ColumnMapping, type ImportField, type ParsedData } from '../../types/setup' | ||
|
|
||
| interface ColumnMappingProps { | ||
| data: ParsedData[] | ||
| headers: string[] | ||
| columnMapping: ColumnMapping | ||
| onBack: () => void | ||
| onNext: () => void | ||
| onMappingChange: (mapping: ColumnMapping) => void | ||
| } | ||
|
|
||
| export function ColumnMappingComponent({ | ||
| data, | ||
| headers, | ||
| columnMapping, | ||
| onBack, | ||
| onNext, | ||
| onMappingChange, | ||
| }: ColumnMappingProps) { | ||
| const sampleData = useMemo(() => data.slice(0, 3), [data]) | ||
|
|
||
| const autoDetectMapping = useMemo(() => { | ||
|
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. The auto-detection only examines the first row of data. Consider sampling multiple rows (e.g., first 5) to improve detection accuracy, especially if the first row might have empty values or unusual formatting. |
||
| const mapping: ColumnMapping = {} | ||
|
|
||
| IMPORT_FIELDS.forEach(field => { | ||
| // Try exact match first | ||
| const exactMatch = headers.find( | ||
| h => h.toLowerCase() === field.label.toLowerCase() || | ||
| h.toLowerCase() === field.key.toLowerCase() | ||
| ) | ||
|
|
||
| if (exactMatch) { | ||
| mapping[field.key] = exactMatch | ||
| return | ||
| } | ||
|
|
||
| // Try partial match | ||
| const partialMatch = headers.find( | ||
| h => h.toLowerCase().includes(field.key.toLowerCase()) || | ||
| field.label.toLowerCase().includes(h.toLowerCase()) | ||
| ) | ||
|
|
||
| if (partialMatch) { | ||
| mapping[field.key] = partialMatch | ||
| } | ||
| }) | ||
|
|
||
| return mapping | ||
| }, [headers]) | ||
|
|
||
| const handleReset = () => { | ||
| onMappingChange(autoDetectMapping) | ||
| } | ||
|
|
||
| const handleHeaderChange = (fieldKey: ImportField['key'], header: string) => { | ||
| onMappingChange({ ...columnMapping, [fieldKey]: header }) | ||
| } | ||
|
|
||
| const mappedCount = Object.keys(columnMapping).filter(k => columnMapping[k as ImportField['key']]).length | ||
| const requiredMapped = IMPORT_FIELDS | ||
| .filter(f => f.required) | ||
| .every(f => columnMapping[f.key]) | ||
|
|
||
| return ( | ||
| <div className="w-full max-w-4xl mx-auto"> | ||
| <div className="text-center mb-8"> | ||
| <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-2"> | ||
| Map Your Columns | ||
| </h2> | ||
| <p className="text-gray-600 dark:text-gray-400"> | ||
| Match your file columns to the system fields | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className="flex items-center justify-between mb-4"> | ||
| <div className="flex items-center gap-2 text-sm"> | ||
| <Columns2 className="w-4 h-4 text-gray-400" /> | ||
| <span className="text-gray-600 dark:text-gray-400"> | ||
| {mappedCount} / {IMPORT_FIELDS.length} fields mapped | ||
| </span> | ||
| </div> | ||
| <Button variant="outline" size="sm" onClick={handleReset}> | ||
| <RefreshCw className="w-4 h-4 mr-2" /> | ||
| Auto-detect | ||
| </Button> | ||
| </div> | ||
|
|
||
| <div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden"> | ||
| <div className="grid grid-cols-2 gap-4 p-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900"> | ||
| <div className="text-sm font-medium text-gray-500 dark:text-gray-400"> | ||
| File Columns | ||
| </div> | ||
| <div className="text-sm font-medium text-gray-500 dark:text-gray-400"> | ||
| System Fields | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="divide-y divide-gray-200 dark:divide-gray-700"> | ||
| {IMPORT_FIELDS.map((field) => ( | ||
| <div key={field.key} className="grid grid-cols-2 gap-4 p-4 items-center"> | ||
| <div> | ||
| <div className="text-sm font-medium text-gray-900 dark:text-white"> | ||
| {field.label} | ||
| {field.required && <span className="text-red-500 ml-1">*</span>} | ||
| </div> | ||
| {field.defaultValue && ( | ||
| <div className="text-xs text-gray-400"> | ||
| Default: {field.defaultValue} | ||
| </div> | ||
| )} | ||
| </div> | ||
| <select | ||
| value={columnMapping[field.key] || ''} | ||
| onChange={(e) => handleHeaderChange(field.key, e.target.value)} | ||
| className="px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg text-sm text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500" | ||
| > | ||
| <option value="">-- Select column --</option> | ||
| {headers.map((header) => ( | ||
| <option key={header} value={header}> | ||
| {header} | ||
| </option> | ||
| ))} | ||
| </select> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Sample data preview */} | ||
| <div className="mt-6 p-4 bg-gray-50 dark:bg-gray-900 rounded-lg"> | ||
| <div className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3"> | ||
| Sample Data Preview | ||
| </div> | ||
| <div className="grid grid-cols-3 gap-4 text-xs"> | ||
| {IMPORT_FIELDS.slice(0, 3).map((field) => { | ||
| const header = columnMapping[field.key] | ||
| const sample = header ? sampleData[0]?.[header] : null | ||
| return ( | ||
| <div key={field.key}> | ||
| <div className="text-gray-500 dark:text-gray-400">{field.label}</div> | ||
| <div className="text-gray-900 dark:text-white font-mono truncate"> | ||
| {sample || '-'} | ||
| </div> | ||
| </div> | ||
| ) | ||
| })} | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="flex justify-between mt-6"> | ||
| <Button variant="outline" onClick={onBack}> | ||
| <ArrowLeft className="w-4 h-4 mr-2" /> | ||
| Back | ||
| </Button> | ||
| <Button onClick={onNext} disabled={!requiredMapped}> | ||
| Continue | ||
| <ArrowRight className="w-4 h-4 ml-2" /> | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
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.
Using a third-party action from
nesalia-inc/marty-action. Ensure this is a trusted action and consider pinning to a specific version tag (e.g.,@v1.0.0) instead of a floating tag to prevent unexpected changes.