super-productivity/docs/add-new-integration.md

8.5 KiB

Adding a New Integration to Super Productivity

This guide explains how to add a new issue tracker integration to Super Productivity.

Overview

Super Productivity supports multiple issue tracker integrations (called "Issue Providers" in the codebase), including GitHub, GitLab, Jira, and others. Adding a new integration requires implementing specific interfaces and services to communicate with the external service.

Integration Architecture

Each integration follows a consistent pattern:

  1. Interface: All integrations implement the IssueServiceInterface, which defines the required methods for communicating with external services.
  2. Provider-specific Models: Each integration defines its own data structures.
  3. API Services: Each integration has an API service that handles HTTP requests.
  4. Common Interfaces Service: Each integration has a service that implements IssueServiceInterface.
  5. Configuration: Each integration defines its configuration options.

Step-by-Step Guide

1. Create the Provider Directory Structure

Create a new directory under src/app/features/issue/providers/ for your integration, for example my-provider/.

2. Create Required Files

Based on existing integrations, you'll need to create:

Model Files

  • my-provider.model.ts - Define your provider's configuration and data structures
  • my-provider-issue.model.ts - Define issue-specific data structures

Example from GitHub:

// github.model.ts
import { BaseIssueProviderCfg } from '../../issue.model';

export interface GithubCfg extends BaseIssueProviderCfg {
  repo: string;
  token?: string;
}

Service Files

  • my-provider-api.service.ts - Handle API communication
  • my-provider-common-interfaces.service.ts - Implement the IssueServiceInterface

Example API service structure:

@Injectable({
  providedIn: 'root',
})
export class MyProviderApiService {
  // HTTP communication methods
  getById$(issueId: string, cfg: MyProviderCfg): Observable<MyProviderIssue> {
    // Implementation
  }

  searchIssues$(searchTerm: string, cfg: MyProviderCfg): Observable<MyProviderIssue[]> {
    // Implementation
  }
}

Example Common Interfaces Service structure:

@Injectable({
  providedIn: 'root',
})
export class MyProviderCommonInterfacesService implements IssueServiceInterface {
  // Implement all required methods from IssueServiceInterface

  isEnabled(cfg: MyProviderCfg): boolean {
    // Implementation
  }

  // Other required methods...
}

Constants File

  • my-provider.const.ts - Define constants and default configurations

Example:

import { ConfigFormSection } from '../../../config/global-config.model';

export const MY_PROVIDER_INITIAL_POLL_DELAY = 5000;
export const MY_PROVIDER_POLL_INTERVAL = 5 * 60 * 1000;

export const DEFAULT_MY_PROVIDER_CFG: MyProviderCfg = {
  isEnabled: false,
  // Other default values
};

export const MY_PROVIDER_CONFIG_FORM_SECTION: ConfigFormSection = {
  // Form configuration
};

Utility Files

  • is-my-provider-enabled.util.ts - Helper for checking if the provider is enabled

Example:

import { MyProviderCfg } from './my-provider.model';

export const isMyProviderEnabled = (cfg: MyProviderCfg): boolean => {
  return cfg && cfg.isEnabled && // other conditions;
};

3. Implement the IssueServiceInterface

The key interface methods that must be implemented include:

// MANDATORY
isEnabled(cfg: IssueIntegrationCfg): boolean;
testConnection$(cfg: IssueIntegrationCfg): Observable<boolean>;
pollTimer$: Observable<number>;
issueLink$(issueId: string | number, issueProviderId: string): Observable<string>;
getById$(id: string | number, issueProviderId: string): Observable<IssueData | null>;
getAddTaskData(issueData: IssueDataReduced): Partial<Task> & { title: string };
searchIssues$(searchTerm: string, issueProviderId: string): Observable<SearchResultItem[]>;
getFreshDataForIssueTask(task: Task): Promise<{ taskChanges: Partial<Task>; issue: IssueData; issueTitle: string; } | null>;
getFreshDataForIssueTasks(tasks: Task[]): Promise<{ task: Task; taskChanges: Partial<Task>; issue: IssueData; }[]>;

// OPTIONAL
getMappedAttachments?(issueData: IssueData): TaskAttachment[];
getNewIssuesToAddToBacklog?(issueProviderId: string, allExistingIssueIds: number[] | string[]): Promise<IssueDataReduced[]>;

4. Update Core Files

You'll need to update several core files to register your new integration:

1. Update issue.model.ts

Add your provider to the IssueProviderKey type:

export type IssueProviderKey =
  | 'JIRA'
  | 'GITHUB'
  | 'GITLAB'
  | 'CALDAV'
  | 'ICAL'
  | 'OPEN_PROJECT'
  | 'GITEA'
  | 'REDMINE'
  | 'MY_PROVIDER'; // Add your provider here

Add your provider configuration to IssueIntegrationCfg:

export type IssueIntegrationCfg =
  | JiraCfg
  | GithubCfg
  | GitlabCfg
  | CaldavCfg
  | CalendarProviderCfg
  | OpenProjectCfg
  | GiteaCfg
  | RedmineCfg
  | MyProviderCfg; // Add your provider here

Update IssueIntegrationCfgs interface:

export interface IssueIntegrationCfgs {
  // should be the same as key IssueProviderKey
  JIRA?: JiraCfg;
  GITHUB?: GithubCfg;
  GITLAB?: GitlabCfg;
  CALDAV?: CaldavCfg;
  CALENDAR?: CalendarProviderCfg;
  OPEN_PROJECT?: OpenProjectCfg;
  GITEA?: GiteaCfg;
  REDMINE?: RedmineCfg;
  MY_PROVIDER?: MyProviderCfg; // Add your provider here
}

Update IssueProvider type:

export type IssueProvider =
  | IssueProviderJira
  | IssueProviderGithub
  | IssueProviderGitlab
  | IssueProviderCaldav
  | IssueProviderCalendar
  | IssueProviderOpenProject
  | IssueProviderGitea
  | IssueProviderRedmine
  | IssueProviderMyProvider; // Add your provider here

2. Update issue.const.ts

Add your provider type constant:

export const MY_PROVIDER_TYPE: IssueProviderKey = 'MY_PROVIDER';

Add your provider to ISSUE_PROVIDER_TYPES:

export const ISSUE_PROVIDER_TYPES: IssueProviderKey[] = [
  GITLAB_TYPE,
  GITHUB_TYPE,
  JIRA_TYPE,
  CALDAV_TYPE,
  ICAL_TYPE,
  OPEN_PROJECT_TYPE,
  GITEA_TYPE,
  REDMINE_TYPE,
  MY_PROVIDER_TYPE, // Add your provider here
];

Update DEFAULT_ISSUE_PROVIDER_CFGS:

export const DEFAULT_ISSUE_PROVIDER_CFGS: IssueIntegrationCfgs = {
  JIRA: DEFAULT_JIRA_CFG,
  GITHUB: DEFAULT_GITHUB_CFG,
  GITLAB: DEFAULT_GITLAB_CFG,
  CALDAV: DEFAULT_CALDAV_CFG,
  CALENDAR: DEFAULT_CALENDAR_CFG,
  OPEN_PROJECT: DEFAULT_OPEN_PROJECT_CFG,
  GITEA: DEFAULT_GITEA_CFG,
  REDMINE: DEFAULT_REDMINE_CFG,
  MY_PROVIDER: DEFAULT_MY_PROVIDER_CFG, // Add your provider here
};

Update ISSUE_PROVIDER_FORM_CFGS_MAP:

export const ISSUE_PROVIDER_FORM_CFGS_MAP: Record<IssueProviderKey, ConfigFormSection> = {
  JIRA: JIRA_CONFIG_FORM_SECTION,
  GITHUB: GITHUB_CONFIG_FORM_SECTION,
  GITLAB: GITLAB_CONFIG_FORM_SECTION,
  CALDAV: CALDAV_CONFIG_FORM_SECTION,
  CALENDAR: CALENDAR_FORM_CFG_NEW,
  OPEN_PROJECT: OPEN_PROJECT_CONFIG_FORM_SECTION,
  GITEA: GITEA_CONFIG_FORM_SECTION,
  REDMINE: REDMINE_CONFIG_FORM_SECTION,
  MY_PROVIDER: MY_PROVIDER_CONFIG_FORM_SECTION, // Add your provider here
};

5. Create UI Components (Optional)

Depending on your integration, you may need to create UI components:

  • Issue display and content components in my-provider/my-provider-issue-content/ directory
  • Header components in my-provider/my-provider-issue-header/ directory
  • Configuration components if needed

6. Register the Provider in the Issue Service

The IssueService uses a provider factory pattern. Ensure your provider service is properly injected and registered.

Testing Your Integration

  1. Run the app with npm run startFrontend
  2. Navigate to the settings page
  3. Add a new integration of your provider type
  4. Test the connection and functionality

Tips and Best Practices

  1. Study Existing Integrations: Use GitHub or GitLab integrations as reference implementations
  2. Error Handling: Implement robust error handling for API failures
  3. Polling: Consider rate limits when implementing polling
  4. Authentication: Securely handle authentication tokens
  5. User Experience: Make configuration and usage as simple as possible

Troubleshooting

  • Check browser console for errors
  • Verify correct implementation of the IssueServiceInterface
  • Ensure all model types are correctly defined
  • Verify correct registration in all required files

Contributing Back

Once your integration is working, please consider submitting it back to the Super Productivity project as a pull request!