Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
294 lines
9.9 KiB
JavaScript
294 lines
9.9 KiB
JavaScript
/**
|
|
* ds-jira-issues.js
|
|
* Jira issue tracker integration
|
|
* View project-specific Jira issues for design system work
|
|
*/
|
|
|
|
import contextStore from '../../stores/context-store.js';
|
|
|
|
export default class JiraIssues extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.state = {
|
|
projectId: null,
|
|
issues: [],
|
|
filterStatus: 'All',
|
|
isLoading: false
|
|
};
|
|
|
|
// Mock data for demo
|
|
this.mockIssues = [
|
|
{ key: 'DSS-234', summary: 'Add Button component variants', status: 'In Progress', type: 'Task', priority: 'High', assignee: 'John Doe' },
|
|
{ key: 'DSS-235', summary: 'Update color token naming convention', status: 'To Do', type: 'Story', priority: 'Medium', assignee: 'Unassigned' },
|
|
{ key: 'DSS-236', summary: 'Fix Card component accessibility', status: 'In Review', type: 'Bug', priority: 'High', assignee: 'Jane Smith' },
|
|
{ key: 'DSS-237', summary: 'Document Typography system', status: 'Done', type: 'Task', priority: 'Low', assignee: 'Mike Johnson' },
|
|
{ key: 'DSS-238', summary: 'Create Icon font export', status: 'To Do', type: 'Task', priority: 'Medium', assignee: 'Sarah Wilson' },
|
|
{ key: 'DSS-239', summary: 'Implement Figma sync automation', status: 'In Progress', type: 'Epic', priority: 'High', assignee: 'John Doe' },
|
|
];
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.render();
|
|
this.setupEventListeners();
|
|
|
|
// Subscribe to project context changes
|
|
this.unsubscribe = contextStore.subscribe(({ state }) => {
|
|
this.state.projectId = state.projectId;
|
|
this.loadIssues();
|
|
});
|
|
|
|
// Load initial issues
|
|
this.state.projectId = contextStore.get('projectId');
|
|
this.loadIssues();
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
if (this.unsubscribe) this.unsubscribe();
|
|
}
|
|
|
|
async loadIssues() {
|
|
this.state.isLoading = true;
|
|
this.renderLoading();
|
|
|
|
// Simulate API call delay
|
|
await new Promise(resolve => setTimeout(resolve, 800));
|
|
|
|
// In real implementation, fetch from Jira API via backend
|
|
this.state.issues = this.mockIssues;
|
|
this.state.isLoading = false;
|
|
this.render();
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
renderLoading() {
|
|
this.innerHTML = `
|
|
<div style="padding: 24px; display: flex; align-items: center; justify-content: center; height: 100%;">
|
|
<div style="text-align: center;">
|
|
<div style="font-size: 24px; margin-bottom: 12px;">⏳</div>
|
|
<div style="font-size: 12px; color: var(--vscode-text-dim);">Loading Jira issues...</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
render() {
|
|
this.innerHTML = `
|
|
<div style="padding: 24px; height: 100%; overflow-y: auto;">
|
|
<div style="margin-bottom: 24px; display: flex; justify-content: space-between; align-items: start;">
|
|
<div>
|
|
<h1 style="margin: 0 0 8px 0; font-size: 24px;">Jira Issues</h1>
|
|
<p style="margin: 0; color: var(--vscode-text-dim);">
|
|
${this.state.projectId ? `Project: ${this.state.projectId}` : 'Select a project to view issues'}
|
|
</p>
|
|
</div>
|
|
<button id="create-issue-btn" style="
|
|
padding: 8px 16px;
|
|
background: var(--vscode-button-background);
|
|
color: var(--vscode-button-foreground);
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
font-size: 12px;
|
|
">+ New Issue</button>
|
|
</div>
|
|
|
|
<!-- Status Filter -->
|
|
<div style="margin-bottom: 24px; display: flex; gap: 8px;">
|
|
${['All', 'To Do', 'In Progress', 'In Review', 'Done'].map(status => `
|
|
<button class="status-filter" data-status="${status}" style="
|
|
padding: 6px 12px;
|
|
background: ${this.state.filterStatus === status ? 'var(--vscode-selection)' : 'var(--vscode-sidebar)'};
|
|
color: var(--vscode-foreground);
|
|
border: 1px solid ${this.state.filterStatus === status ? 'var(--vscode-focusBorder)' : 'var(--vscode-border)'};
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
">${status}</button>
|
|
`).join('')}
|
|
</div>
|
|
|
|
<!-- Issues List -->
|
|
<div style="display: flex; flex-direction: column; gap: 12px;">
|
|
${this.renderIssuesList()}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
renderIssuesList() {
|
|
const filtered = this.state.filterStatus === 'All'
|
|
? this.state.issues
|
|
: this.state.issues.filter(i => i.status === this.state.filterStatus);
|
|
|
|
if (filtered.length === 0) {
|
|
return `
|
|
<div style="
|
|
background: var(--vscode-sidebar);
|
|
border: 1px solid var(--vscode-border);
|
|
border-radius: 4px;
|
|
padding: 24px;
|
|
text-align: center;
|
|
color: var(--vscode-text-dim);
|
|
">
|
|
No issues found in this status
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return filtered.map(issue => `
|
|
<div class="jira-issue" data-issue-key="${issue.key}" style="
|
|
background: var(--vscode-sidebar);
|
|
border: 1px solid var(--vscode-border);
|
|
border-radius: 4px;
|
|
padding: 16px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
">
|
|
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;">
|
|
<div style="display: flex; gap: 12px; align-items: start; flex: 1;">
|
|
<!-- Issue Type Badge -->
|
|
<div style="
|
|
padding: 4px 8px;
|
|
background: ${this.getTypeColor(issue.type)};
|
|
color: white;
|
|
border-radius: 3px;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
min-width: 50px;
|
|
text-align: center;
|
|
">${issue.type}</div>
|
|
|
|
<!-- Issue Content -->
|
|
<div style="flex: 1;">
|
|
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 6px;">
|
|
<span style="font-family: monospace; font-weight: 600; color: #0066CC;">
|
|
${issue.key}
|
|
</span>
|
|
<span style="font-weight: 500; font-size: 13px;">
|
|
${issue.summary}
|
|
</span>
|
|
</div>
|
|
<div style="display: flex; gap: 12px; font-size: 11px; color: var(--vscode-text-dim);">
|
|
<span>Assignee: ${issue.assignee}</span>
|
|
<span>Priority: ${issue.priority}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Badge -->
|
|
<div style="
|
|
padding: 4px 12px;
|
|
background: var(--vscode-bg);
|
|
border: 1px solid var(--vscode-border);
|
|
border-radius: 3px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
">${issue.status}</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div style="display: flex; gap: 8px; padding-top: 12px; border-top: 1px solid var(--vscode-border);">
|
|
<button class="open-issue-btn" style="
|
|
padding: 4px 12px;
|
|
background: var(--vscode-button-secondaryBackground);
|
|
color: var(--vscode-button-secondaryForeground);
|
|
border: none;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
font-size: 11px;
|
|
">Open in Jira</button>
|
|
<button class="link-pr-btn" style="
|
|
padding: 4px 12px;
|
|
background: var(--vscode-button-secondaryBackground);
|
|
color: var(--vscode-button-secondaryForeground);
|
|
border: none;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
font-size: 11px;
|
|
">Link PR</button>
|
|
<button class="assign-btn" style="
|
|
padding: 4px 12px;
|
|
background: var(--vscode-button-secondaryBackground);
|
|
color: var(--vscode-button-secondaryForeground);
|
|
border: none;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
font-size: 11px;
|
|
">Assign</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
getTypeColor(type) {
|
|
const colors = {
|
|
'Bug': '#f44336',
|
|
'Task': '#2196f3',
|
|
'Story': '#4caf50',
|
|
'Epic': '#9c27b0',
|
|
'Subtask': '#ff9800'
|
|
};
|
|
return colors[type] || '#999';
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Status filters
|
|
this.querySelectorAll('.status-filter').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
this.state.filterStatus = btn.dataset.status;
|
|
this.render();
|
|
this.setupEventListeners();
|
|
});
|
|
});
|
|
|
|
// Create issue button
|
|
const createBtn = this.querySelector('#create-issue-btn');
|
|
if (createBtn) {
|
|
createBtn.addEventListener('click', () => {
|
|
this.dispatchEvent(new CustomEvent('create-issue', {
|
|
detail: { projectId: this.state.projectId },
|
|
bubbles: true,
|
|
composed: true
|
|
}));
|
|
});
|
|
}
|
|
|
|
// Issue actions
|
|
this.querySelectorAll('.open-issue-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const issueKey = e.target.closest('.jira-issue').dataset.issueKey;
|
|
window.open(`https://jira.atlassian.net/browse/${issueKey}`, '_blank');
|
|
});
|
|
});
|
|
|
|
this.querySelectorAll('.link-pr-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const issueKey = e.target.closest('.jira-issue').dataset.issueKey;
|
|
this.dispatchEvent(new CustomEvent('link-pr', {
|
|
detail: { issueKey },
|
|
bubbles: true,
|
|
composed: true
|
|
}));
|
|
});
|
|
});
|
|
|
|
this.querySelectorAll('.assign-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const issueKey = e.target.closest('.jira-issue').dataset.issueKey;
|
|
this.dispatchEvent(new CustomEvent('assign-issue', {
|
|
detail: { issueKey },
|
|
bubbles: true,
|
|
composed: true
|
|
}));
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
customElements.define('ds-jira-issues', JiraIssues);
|