Addon Categories

DataSources

Build robust integrations for querying tasks and submitting time entries.

DataSources (Data Providers)

A DataSource is an addon that integrates Mr. Tick with task and project management tools such as Jira, Redmine, YouTrack, GitHub Issues, or Azure DevOps.

It provides the communication bridge used by the Synchronization Engine for Pull (downloading tasks and metadata) and Push (uploading time entries).


🏗️ DataSource Architecture

A modern Mr. Tick DataSource is registered in the addon's activate method and implements createInstance(context), returning an instance with its Providers:

  1. authStrategy (IAuthenticationStrategy): Authenticates and validates credentials.
  2. tasksProvider (ITaskProvider): Lists tasks and performs checkpoint-based Pull.
  3. timeEntriesProvider (ITimeEntryProvider): Lists, creates, updates, and deletes time entries (Push).
  4. membersProvider (IMemberProvider): Returns information about the connected member and users.
  5. metadataProvider (IMetadataProvider): Retrieves available statuses, priorities, and activities.

💻 Practical Implementation Example

The following example shows a complete, decoupled DataSource:

import type {
  IAddon,
  AddonContext,
  IDataSource,
  DataSourceContext,
  IDataSourceInstance,
  ITaskProvider,
  ITimeEntryProvider,
  IMemberProvider,
  IMetadataProvider,
  IAuthenticationStrategy,
  TaskDTO,
  TimeEntryDTO,
  MemberDTO,
  MetadataDTO,
  PagedResultDTO,
  Task,
  TimeEntry,
  CreatedTaskResult,
  UpdatedTaskResult,
  CreatedTimeEntryResult,
  UpdatedTimeEntryResult,
} from '@mr-tick/sdk'

// 1. Providers Especializados
class MinhaApiTaskProvider implements ITaskProvider {
  constructor(private context: DataSourceContext) {}

  async pull(
    memberId: string,
    checkpoint: { updatedAt: Date; id: string },
    batch: number,
  ): Promise<TaskDTO[]> {
    // Busca tarefas na API remota respeitando o checkpoint e batch
    return []
  }

  async findAll(): Promise<PagedResultDTO<TaskDTO>> {
    return { items: [], total: 0, page: 1, pageSize: 50 }
  }

  async findById(id: string): Promise<TaskDTO | undefined> {
    return undefined
  }

  async create(task: Task): Promise<CreatedTaskResult> {
    // Envia criação para a API e retorna o ID e carimbo gerados pelo servidor
    return { id: 'task-123', updatedAt: new Date() }
  }

  async update(task: Task): Promise<UpdatedTaskResult> {
    // Atualiza na API e retorna o ID e carimbo de atualização confirmado pelo servidor
    return { id: task.id, updatedAt: new Date() }
  }

  async delete(id: string): Promise<void> {
    // Exclui a tarefa na API remota
  }
}

class MinhaApiTimeEntryProvider implements ITimeEntryProvider {
  constructor(private context: DataSourceContext) {}

  async pull(
    memberId: string,
    checkpoint: { updatedAt: Date; id: string },
    batch: number,
  ): Promise<TimeEntryDTO[]> {
    return []
  }

  async findByMemberId(
    memberId: string,
    startDate: Date,
    endDate: Date,
  ): Promise<PagedResultDTO<TimeEntryDTO>> {
    return { items: [], total: 0, page: 1, pageSize: 50 }
  }

  async create(entry: TimeEntry): Promise<CreatedTimeEntryResult> {
    // Envia novo apontamento para a API e retorna o ID e updatedAt gerados pelo servidor
    return { id: '10423', updatedAt: new Date() }
  }

  async update(entry: TimeEntry): Promise<UpdatedTimeEntryResult> {
    // Atualiza apontamento existente na API e retorna confirmação do servidor
    return { id: entry.id, updatedAt: new Date() }
  }

  async delete(id: string): Promise<void> {
    // Remove o apontamento na API
  }

  async findById(id: string): Promise<TimeEntry | undefined> {
    return undefined
  }

  async findAll(): Promise<PagedResultDTO<TimeEntryDTO>> {
    return { items: [], total: 0, page: 1, pageSize: 50 }
  }
}

// 2. Definição do DataSource (Metadata-Driven)
export const MeuDataSource: IDataSource = {
  getConnectionSchema(): AddonSettingsSchema {
    return [
      {
        id: 'credentials',
        label: 'Credenciais & Conexão',
        groups: [
          {
            id: 'auth',
            label: 'Autenticação',
            fields: [
              {
                id: 'serverUrl',
                label: 'URL do Servidor',
                type: 'text',
                scope: 'configuration',
                required: true,
              },
              {
                id: 'apiToken',
                label: 'Token de Acesso',
                type: 'password',
                scope: 'credential',
                required: true,
              },
            ],
          },
        ],
      },
    ]
  },

  createInstance(context: DataSourceContext): IDataSourceInstance {
    return {
      authStrategy: new MinhaAuthStrategy(context),
      tasksProvider: new MinhaApiTaskProvider(context),
      timeEntriesProvider: new MinhaApiTimeEntryProvider(context),
      membersProvider: new MinhaApiMemberProvider(context),
      metadataProvider: new MinhaApiMetadataProvider(context),
    }
  },
}

// 3. Addon Principal (Microkernel: apenas activate e deactivate)
export default class MeuDataSourceAddon implements IAddon {
  public async activate(context: AddonContext): Promise<void> {
    // Registra as preferências dinamicamente ou reaproveita o schema
    context.contributions.settings.register(MeuDataSource.getConnectionSchema())

    // Registra o DataSource no sistema
    context.contributions.dataSources.register(MeuDataSource)
  }

  public async deactivate(): Promise<void> {
    console.log('[MeuDataSourceAddon] Desativado.')
  }
}

💡 DataSource Best Practices

  • Separate Providers: Put each responsibility (Tasks, Time Entries, Members, and Metadata) in a specialized class implementing the corresponding interface.
  • Respect Checkpoints: Use modification-date filters (checkpoint.updatedAt) to minimize traffic during pull.
  • Validate Credentials: Authenticate in authStrategy so invalid connections are rejected before synchronization begins.
  • Declare Settings: Use context.contributions.settings.register(...) for credential and configuration fields without mixing them with network logic.