Azure DevOps Boards

プロジェクト管理チケット管理アジャイル開発MicrosoftAzureDevOpsエンタープライズ

チケット管理ツール

Azure DevOps Boards

概要

Azure DevOps Boardsは、Microsoft Azure DevOpsプラットフォームの一部として提供されるエンタープライズグレードのプロジェクト管理・チケット追跡ツールです。アジャイル開発手法(Scrum、Kanban、CMMI)をフルサポートし、要件から展開まで全工程の追跡機能を提供します。Microsoft Teamsとの深い統合、豊富なレポート機能、エンタープライズセキュリティにより、大規模開発チームのプロジェクト管理を効率化します。

詳細

Azure DevOps Boardsは、Microsoft の長年にわたるエンタープライズ開発支援ノウハウを結集し、現代のアジャイル開発ニーズに対応する設計が特徴です。

主要な特徴

  • 包括的なアジャイル対応: Scrum、Kanban、CMMI、Basicの4つのプロセステンプレート
  • エンドツーエンド追跡: 要件定義から本番展開まで各ステップでの意思決定と進捗を完全追跡
  • 強力な統合機能: Azure Pipelines、Azure Repos、Microsoft Teams、Slackとの統合
  • 高度な可視化: インタラクティブなダッシュボード、ガントチャート、バーンダウンチャート
  • エンタープライズ機能: RBAC、LDAP/AD統合、監査ログ、コンプライアンス対応

技術仕様

  • アーキテクチャ: Azure クラウドインフラ、99.9%可用性SLA
  • セキュリティ: ISO 27001、SOC 2準拠、Azure AD統合
  • スケーラビリティ: 無制限プロジェクト・ユーザー対応、グローバル展開
  • 拡張性: Azure DevOps Marketplace、REST API、カスタム拡張

メリット・デメリット

メリット

  1. Microsoft エコシステムとの統合

    • Office 365、Azure AD、Microsoft Teamsとのシームレス連携
    • Visual Studio、VS Codeとの深い統合
  2. コストパフォーマンス

    • 5ユーザーまで無料、追加ユーザー月額約6ドル
    • エンタープライズ機能も比較的低コスト
  3. エンタープライズグレードの信頼性

    • Microsoft の運用ノウハウによる高い可用性
    • 厳格なセキュリティとコンプライアンス対応
  4. 豊富なプロセステンプレート

    • 組織の成熟度に応じた柔軟なプロセス選択
    • カスタムワークフロー作成機能

デメリット

  1. Microsoft 環境依存

    • Windows/.NET中心の開発環境で最大効果
    • オープンソース・Linux環境では一部制約
  2. 機能の複雑さ

    • 豊富な機能ゆえの学習コスト
    • 初期設定の複雑さと専門知識の必要性
  3. カスタマイズの制約

    • テンプレート変更に一定の制限
    • UI/UXのカスタマイズ範囲が限定的

参考ページ

基本的な使い方

1. プロジェクト作成と初期設定

// Azure DevOps REST API を使用したプロジェクト作成
const createProject = async (organizationUrl, projectData) => {
  const response = await fetch(`${organizationUrl}/_apis/projects?api-version=7.0`, {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: projectData.name,
      description: projectData.description,
      visibility: projectData.visibility || 'private',
      capabilities: {
        versioncontrol: {
          sourceControlType: 'Git'
        },
        processTemplate: {
          templateTypeId: projectData.processTemplate || 'adcc42ab-9882-485e-a3ed-7678f01f66bc' // Scrum
        }
      }
    })
  });
  
  const operation = await response.json();
  
  // プロジェクト作成完了を待機
  const project = await waitForOperation(organizationUrl, operation.id);
  
  // チーム設定
  await setupTeams(organizationUrl, project.name, projectData.teams);
  
  return project;
};

const setupTeams = async (organizationUrl, projectName, teamsConfig) => {
  for (const teamConfig of teamsConfig) {
    // チーム作成
    const team = await fetch(
      `${organizationUrl}/_apis/projects/${projectName}/teams?api-version=7.0`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: teamConfig.name,
          description: teamConfig.description
        })
      }
    );
    
    const teamData = await team.json();
    
    // チームにメンバー追加
    for (const member of teamConfig.members) {
      await addTeamMember(organizationUrl, projectName, teamData.name, member);
    }
  }
};

2. ワークアイテム管理とカスタムフィールド

// ワークアイテム作成とカスタムフィールド設定
const createWorkItem = async (organizationUrl, projectName, workItemData) => {
  const patchDocument = [
    {
      op: 'add',
      path: '/fields/System.Title',
      value: workItemData.title
    },
    {
      op: 'add',
      path: '/fields/System.Description',
      value: workItemData.description
    },
    {
      op: 'add',
      path: '/fields/System.AreaPath',
      value: workItemData.areaPath || projectName
    },
    {
      op: 'add',
      path: '/fields/System.IterationPath',
      value: workItemData.iterationPath || projectName
    },
    {
      op: 'add',
      path: '/fields/System.AssignedTo',
      value: workItemData.assignedTo
    },
    {
      op: 'add',
      path: '/fields/Microsoft.VSTS.Common.Priority',
      value: workItemData.priority || 2
    }
  ];
  
  // カスタムフィールド追加
  if (workItemData.customFields) {
    for (const [fieldName, value] of Object.entries(workItemData.customFields)) {
      patchDocument.push({
        op: 'add',
        path: `/fields/${fieldName}`,
        value: value
      });
    }
  }
  
  const response = await fetch(
    `${organizationUrl}/${projectName}/_apis/wit/workitems/$${workItemData.type}?api-version=7.0`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
        'Content-Type': 'application/json-patch+json'
      },
      body: JSON.stringify(patchDocument)
    }
  );
  
  const workItem = await response.json();
  
  // 関連づけ設定
  if (workItemData.relatedItems) {
    await createWorkItemRelations(organizationUrl, workItem.id, workItemData.relatedItems);
  }
  
  return workItem;
};

const createWorkItemRelations = async (organizationUrl, workItemId, relations) => {
  for (const relation of relations) {
    const patchDocument = [{
      op: 'add',
      path: '/relations/-',
      value: {
        rel: relation.type, // 'System.LinkTypes.Hierarchy-Forward', 'System.LinkTypes.Dependency-Forward'
        url: `${organizationUrl}/_apis/wit/workItems/${relation.targetId}`,
        attributes: relation.attributes || {}
      }
    }];
    
    await fetch(
      `${organizationUrl}/_apis/wit/workitems/${workItemId}?api-version=7.0`,
      {
        method: 'PATCH',
        headers: {
          'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
          'Content-Type': 'application/json-patch+json'
        },
        body: JSON.stringify(patchDocument)
      }
    );
  }
};

3. スプリント管理とアジャイルボード

// スプリント作成とバックログ管理
const setupSprintManagement = async (organizationUrl, projectName, teamName, sprintConfig) => {
  // イテレーション作成
  const iteration = await fetch(
    `${organizationUrl}/${projectName}/_apis/work/teamsettings/iterations?api-version=7.0`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: sprintConfig.name,
        path: `${projectName}\\${sprintConfig.name}`,
        attributes: {
          startDate: sprintConfig.startDate,
          finishDate: sprintConfig.endDate
        }
      })
    }
  );
  
  const iterationData = await iteration.json();
  
  // チームキャパシティ設定
  await setTeamCapacity(organizationUrl, projectName, teamName, iterationData.id, sprintConfig.capacity);
  
  // スプリントバックログアイテム追加
  if (sprintConfig.workItems) {
    await addWorkItemsToSprint(organizationUrl, projectName, teamName, iterationData.id, sprintConfig.workItems);
  }
  
  return iterationData;
};

const setTeamCapacity = async (organizationUrl, projectName, teamName, iterationId, capacityData) => {
  for (const member of capacityData) {
    await fetch(
      `${organizationUrl}/${projectName}/${teamName}/_apis/work/teamsettings/iterations/${iterationId}/capacities/${member.teamMemberId}?api-version=7.0`,
      {
        method: 'PUT',
        headers: {
          'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          activities: member.activities,
          daysOff: member.daysOff || []
        })
      }
    );
  }
};

4. ダッシュボードとレポート生成

// カスタムダッシュボード作成とウィジェット設定
const createProjectDashboard = async (organizationUrl, projectName, dashboardConfig) => {
  // ダッシュボード作成
  const dashboard = await fetch(
    `${organizationUrl}/${projectName}/_apis/dashboard/dashboards?api-version=7.0`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: dashboardConfig.name,
        description: dashboardConfig.description,
        groupId: dashboardConfig.teamId
      })
    }
  );
  
  const dashboardData = await dashboard.json();
  
  // ウィジェット設定
  const widgets = [
    {
      name: 'Sprint Burndown',
      contributionId: 'ms.vss-dashboards-web.Microsoft.VisualStudioOnline.Dashboards.BurndownWidget',
      settings: JSON.stringify({
        teamId: dashboardConfig.teamId,
        iterationId: dashboardConfig.currentIterationId
      }),
      size: { rowSpan: 2, columnSpan: 3 },
      position: { row: 1, column: 1 }
    },
    {
      name: 'Velocity Chart',
      contributionId: 'ms.vss-dashboards-web.Microsoft.VisualStudioOnline.Dashboards.VelocityWidget',
      settings: JSON.stringify({
        teamId: dashboardConfig.teamId,
        iterationCount: 6
      }),
      size: { rowSpan: 2, columnSpan: 3 },
      position: { row: 1, column: 4 }
    },
    {
      name: 'Work Item Chart',
      contributionId: 'ms.vss-dashboards-web.Microsoft.VisualStudioOnline.Dashboards.WitChartWidget',
      settings: JSON.stringify({
        query: `SELECT [System.Id], [System.Title], [System.State] FROM WorkItems WHERE [System.TeamProject] = '${projectName}' AND [System.WorkItemType] = 'User Story'`,
        chartType: 'pieChart',
        groupBy: 'System.State'
      }),
      size: { rowSpan: 2, columnSpan: 2 },
      position: { row: 3, column: 1 }
    }
  ];
  
  // ウィジェット追加
  for (const widget of widgets) {
    await fetch(
      `${organizationUrl}/${projectName}/_apis/dashboard/dashboards/${dashboardData.id}/widgets?api-version=7.0`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(widget)
      }
    );
  }
  
  return dashboardData;
};

5. クエリとフィルタリング

// 高度なワークアイテムクエリとレポート
const executeAdvancedQueries = async (organizationUrl, projectName, queryConfig) => {
  // カスタムクエリ作成
  const createCustomQuery = async (queryData) => {
    return await fetch(
      `${organizationUrl}/${projectName}/_apis/wit/queries/${queryData.folderId}?api-version=7.0`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          name: queryData.name,
          wiql: queryData.wiql,
          isPublic: queryData.isPublic || false
        })
      }
    );
  };
  
  // 定期レポート用クエリ
  const reportQueries = {
    sprintProgress: `
      SELECT [System.Id], [System.Title], [System.State], [System.AssignedTo], 
             [Microsoft.VSTS.Scheduling.StoryPoints], [Microsoft.VSTS.Common.Priority]
      FROM WorkItems 
      WHERE [System.TeamProject] = '${projectName}'
        AND [System.IterationPath] UNDER '${projectName}\\Current'
        AND [System.WorkItemType] IN ('User Story', 'Bug', 'Task')
      ORDER BY [Microsoft.VSTS.Common.Priority] DESC, [System.Id]
    `,
    
    bugTrend: `
      SELECT [System.Id], [System.Title], [System.CreatedDate], [System.State], 
             [Microsoft.VSTS.Common.Severity], [System.AssignedTo]
      FROM WorkItems 
      WHERE [System.TeamProject] = '${projectName}'
        AND [System.WorkItemType] = 'Bug'
        AND [System.CreatedDate] >= @StartOfMonth('-1M')
      ORDER BY [System.CreatedDate] DESC
    `,
    
    teamVelocity: `
      SELECT [System.Id], [System.Title], [System.IterationPath], 
             [Microsoft.VSTS.Scheduling.StoryPoints], [System.State]
      FROM WorkItems 
      WHERE [System.TeamProject] = '${projectName}'
        AND [System.WorkItemType] = 'User Story'
        AND [System.State] = 'Closed'
        AND [System.ChangedDate] >= @StartOfMonth('-3M')
      ORDER BY [System.IterationPath], [System.Id]
    `
  };
  
  // クエリ実行と結果取得
  const executeQuery = async (wiql) => {
    const queryResult = await fetch(
      `${organizationUrl}/${projectName}/_apis/wit/wiql?api-version=7.0`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ query: wiql })
      }
    );
    
    const result = await queryResult.json();
    
    // ワークアイテム詳細取得
    if (result.workItems && result.workItems.length > 0) {
      const ids = result.workItems.map(wi => wi.id).join(',');
      const detailsResponse = await fetch(
        `${organizationUrl}/_apis/wit/workitems?ids=${ids}&fields=System.Id,System.Title,System.State,System.AssignedTo,Microsoft.VSTS.Scheduling.StoryPoints&api-version=7.0`,
        {
          headers: {
            'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`
          }
        }
      );
      
      return await detailsResponse.json();
    }
    
    return result;
  };
  
  // 全クエリ実行
  const results = {};
  for (const [queryName, wiql] of Object.entries(reportQueries)) {
    results[queryName] = await executeQuery(wiql);
  }
  
  return results;
};

6. 統合とAPI活用

// Microsoft Teams統合とSlack連携
const setupIntegrations = async (organizationUrl, projectName, integrationConfig) => {
  // Service Hook設定(Slack通知)
  const setupSlackWebhook = async (slackConfig) => {
    return await fetch(
      `${organizationUrl}/_apis/hooks/subscriptions?api-version=7.0`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          publisherId: 'tfs',
          eventType: 'workitem.updated',
          resourceVersion: '1.0',
          consumerId: 'webHooks',
          consumerActionId: 'httpRequest',
          publisherInputs: {
            projectId: projectName,
            workItemType: 'User Story'
          },
          consumerInputs: {
            url: slackConfig.webhookUrl,
            httpHeaders: 'Content-Type: application/json',
            messages: JSON.stringify({
              workItemUpdated: {
                text: 'Work item updated: {{workitem.fields.System.Title}} - {{workitem.fields.System.State}}'
              }
            }),
            detailedMessagesToSend: 'workItemUpdated',
            messagesToSend: 'all'
          }
        })
      }
    );
  };
  
  // Azure Pipelines連携設定
  const linkBuildPipeline = async (pipelineConfig) => {
    return await fetch(
      `${organizationUrl}/${projectName}/_apis/pipelines/build/definitions/${pipelineConfig.definitionId}/settings?api-version=7.0`,
      {
        method: 'PUT',
        headers: {
          'Authorization': `Basic ${Buffer.from(`:${PAT_TOKEN}`).toString('base64')}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          statusBadgeEnabled: true,
          autoLinkWorkItems: true,
          reportBuildStatus: true
        })
      }
    );
  };
  
  // Power BI連携レポート設定
  const setupPowerBIIntegration = async (powerBIConfig) => {
    const analyticsUrl = `${organizationUrl}/${projectName}/_odata/v3.0-preview/WorkItems?$select=WorkItemId,Title,WorkItemType,State,CreatedDate,ChangedDate,StoryPoints&$filter=Project/ProjectName eq '${projectName}'`;
    
    return {
      analyticsUrl,
      oDataEndpoint: `${organizationUrl}/${projectName}/_odata/v3.0-preview`,
      powerBITemplate: powerBIConfig.templatePath,
      refreshSchedule: powerBIConfig.refreshSchedule || 'daily'
    };
  };
  
  return {
    setupSlackWebhook,
    linkBuildPipeline,
    setupPowerBIIntegration
  };
};

Azure DevOps Boardsは、Microsoft のエンタープライズ開発ノウハウとクラウド技術を結集した、信頼性の高いプロジェクト管理プラットフォームです。Azure エコシステムとの深い統合により、開発からデプロイまで一貫したDevOpsワークフローを実現します。