Skip to content

Commit

Permalink
Merge pull request #274 from jasperapp/fix-deprecated-github-project-…
Browse files Browse the repository at this point in the history
…next-api

ProjectNextAPIをProjectV2APIに移行した
  • Loading branch information
h13i32maru authored Nov 20, 2022
2 parents ed3f8a6 + e060666 commit a91e79a
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 105 deletions.
20 changes: 10 additions & 10 deletions src/Renderer/Fragment/StreamSetup/StreamSetupLoadingFragment.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, {useEffect, useState} from 'react';
import {ProjectProp, StreamSetupBody, StreamSetupDesc, StreamSetupFooter} from './StreamSetupCommon';
import {Button} from '../../Library/View/Button';
import {View} from '../../Library/View/View';
import {UserPrefRepo} from '../../Repository/UserPrefRepo';
import {GitHubSearchClient} from '../../Library/GitHub/GitHubSearchClient';
import {GitHubUserClient} from '../../Library/GitHub/GitHubUserClient';
import {RemoteIssueEntity} from '../../Library/Type/RemoteGitHubV3/RemoteIssueEntity';
import {GitHubV4IssueClient} from '../../Library/GitHub/V4/GitHubV4IssueClient';
import {GitHubSearchClient} from '../../Library/GitHub/GitHubSearchClient';
import {Loading} from '../../Library/View/Loading';
import {RemoteIssueEntity} from '../../Library/Type/RemoteGitHubV3/RemoteIssueEntity';
import {DateUtil} from '../../Library/Util/DateUtil';
import {Button} from '../../Library/View/Button';
import {Loading} from '../../Library/View/Loading';
import {Translate} from '../../Library/View/Translate';
import {View} from '../../Library/View/View';
import {UserPrefRepo} from '../../Repository/UserPrefRepo';
import {ProjectProp, StreamSetupBody, StreamSetupDesc, StreamSetupFooter} from './StreamSetupCommon';

type Props = {
show: boolean;
Expand Down Expand Up @@ -116,9 +116,9 @@ async function fetchProjects(remoteIssues: RemoteIssueEntity[]): Promise<{error?
projectMap[projectCard.project.url] = {url: projectCard.project.url, title: projectCard.project.name};
});

issue.projectNextItems?.nodes?.forEach(projectNextItem => {
projectNextItem.fieldValues?.nodes?.forEach(fieldValue => {
projectMap[fieldValue.projectField.project.url] = {url: fieldValue.projectField.project.url, title: fieldValue.projectField.project.title};
issue.projectItems?.nodes?.forEach(projectItem => {
projectItem.fieldValues?.nodes?.forEach(fieldValue => {
if (fieldValue.field != null) projectMap[fieldValue.field.project.url] = {url: fieldValue.field.project.url, title: fieldValue.field.project.title};
});
});
});
Expand Down
99 changes: 58 additions & 41 deletions src/Renderer/Library/GitHub/V4/GitHubV4IssueClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,35 +124,38 @@ export class GitHubV4IssueClient extends GitHubV4Client {
}

private static getProjectFields(v4Issue: RemoteGitHubV4IssueEntity): RemoteProjectFieldEntity[] {
return v4Issue.projectNextItems.nodes.flatMap(item => item.fieldValues.nodes).map<RemoteProjectFieldEntity>(fieldValue => {
const dataType = fieldValue.projectField.dataType;
const value = fieldValue.value;
const name = fieldValue.projectField.name;
const settings = JSON.parse(fieldValue.projectField.settings);
const projectTitle = fieldValue.projectField.project.title;
const projectUrl = fieldValue.projectField.project.url;

if (value == null) return null;

if (dataType === 'SINGLE_SELECT' && settings.options != null) {
const realValue = settings.options.find(option => option.id === value);
if (realValue != null) return {name, value: realValue.name as string, projectTitle, projectUrl, dataType};
return v4Issue.projectItems.nodes.flatMap(item => item.fieldValues.nodes).map<RemoteProjectFieldEntity>(fieldValue => {
// github projectのfiledValueには多くの型があり、Jasperでは特定の型(__typename)にしか対応していない。
// 未対応の型の場合はemptyになるのでチェックしている。
if (fieldValue.field == null) return null;

const dataType = fieldValue.field.dataType;
const name = fieldValue.field.name;
const projectTitle = fieldValue.field.project.title;
const projectUrl = fieldValue.field.project.url;

if (dataType === 'SINGLE_SELECT' && fieldValue.name != null) {
return {name, value: fieldValue.name, projectTitle, projectUrl, dataType};
}

if (dataType === 'ITERATION' && settings.configuration != null) {
const realValue = settings.configuration.iterations.find(iteration => iteration.id === value);
if (realValue != null) return {name, value: realValue.title as string, projectTitle, projectUrl, dataType};
if (dataType === 'ITERATION' && fieldValue.title != null) {
return {name, value: fieldValue.title, projectTitle, projectUrl, dataType};
}

if (dataType == 'DATE') {
if (dataType == 'DATE' && fieldValue.date != null) {
// value is `2022-01-20T00:00:00`
return {name, value: value.split('T')[0] as string, projectTitle, projectUrl, dataType};
return {name, value: fieldValue.date.split('T')[0], projectTitle, projectUrl, dataType};
}

if (dataType == 'NUMBER' && fieldValue.number != null) {
// value is `2022-01-20T00:00:00`
return {name, value: fieldValue.number.toString(), projectTitle, projectUrl, dataType};
}

// titleは「何もfiledがついていないissueのprojectTitle, projectUrlを認識する」ために必要。
// もしtitleをハンドリングしないと、何もfieldがついてないissueのprojectTitle, projectUrlを判別できなくなってしまう。
if (dataType === 'TITLE' || dataType === 'TEXT' || dataType === 'NUMBER') {
return {name, value, projectTitle, projectUrl, dataType};
if (dataType === 'TITLE' || dataType === 'TEXT') {
return {name, value: fieldValue.text, projectTitle, projectUrl, dataType};
}

return null;
Expand All @@ -163,25 +166,24 @@ export class GitHubV4IssueClient extends GitHubV4Client {
// iterationなfiledをfilterでうまく使えるように日付を展開した状態にする
// 例: {name: 'sprint', value: '2022-01-01,2022-01-02,2022-01-03'} のようにする
private static getProjectExpandedIterationField(v4Issue: RemoteGitHubV4IssueEntity): RemoteProjectFieldEntity[] {
return v4Issue.projectNextItems.nodes
return v4Issue.projectItems.nodes
.flatMap(item => item.fieldValues.nodes)
.filter(fieldValue => fieldValue.projectField.dataType === 'ITERATION')
.filter(fieldValue => fieldValue.field?.dataType === 'ITERATION')
.map(iterationFieldValue => {
const settings = JSON.parse(iterationFieldValue.projectField.settings);
const iteration = settings?.configuration?.iterations.find(iteration => iteration.id === iterationFieldValue.value);
if (iteration == null) return null;
const {title, duration, startDate} = iterationFieldValue;
if (title == null || duration == null || startDate == null) return null;

const dateList: string[] = [];
for (let i = 0; i < iteration.duration; i++) {
const date = dayjs(iteration.start_date).add(i, 'day').format('YYYY-MM-DD');
for (let i = 0; i < duration; i++) {
const date = dayjs(startDate).add(i, 'day').format('YYYY-MM-DD');
dateList.push(date);
}

return {
name: iterationFieldValue.projectField.name,
name: iterationFieldValue.field.name,
value: dateList.join(','),
projectTitle: iterationFieldValue.projectField.project.title,
projectUrl: iterationFieldValue.projectField.project.url,
projectTitle: iterationFieldValue.field.project.title,
projectUrl: iterationFieldValue.field.project.url,
dataType: 'EXPANDED_ITERATION',
};
})
Expand Down Expand Up @@ -274,14 +276,14 @@ export class GitHubV4IssueClient extends GitHubV4Client {
// 現時点(2022-08-17)、GHEはGitHub Project v2に対応していないので、レスポンスが得られない。
// 空の値を入れておくことでNPEを防ぐ。
issues.forEach(issue => {
if (issue.projectNextItems == null) issue.projectNextItems = {nodes: []};
if (issue.projectItems == null) issue.projectItems = {nodes: []};
});

// 現時点(2022-08-17)では、OAuthアプリでorgのissueのgithubプロジェクト情報を取得できず、値にnullが入ってくる。
// そのためここでフィルターしておく。
// 再現方法: jasperをoauthアプリ承認していないorgで、パブリックなgithubプロジェクトに紐付いたパブリックリポジトリのissueからgithubプロジェクト情報を読み取ると再現する。
issues.forEach(issue => {
issue.projectNextItems.nodes = issue.projectNextItems.nodes.filter(item => item != null);
issue.projectItems.nodes = issue.projectItems.nodes.filter(item => item != null);
});

const foundNodeIds = issues.map(issue => issue.node_id);
Expand Down Expand Up @@ -496,20 +498,35 @@ const COMMON_QUERY_TEMPLATE = `
`;

const GITHUB_PROJECT_V2 = `
projectNextItems(first: 100) {
projectItems(first: 100) {
nodes {
fieldValues(first: 100) {
nodes {
projectField {
# title, text
... on ProjectV2ItemFieldTextValue {
text
field { ... on ProjectV2Field {name dataType project {url title}} }
}
# single_select
... on ProjectV2ItemFieldSingleSelectValue {
name
settings
dataType
project {
title
url
}
field {... on ProjectV2SingleSelectField { name dataType project {url title} options { name } } }
}
# iteration
... on ProjectV2ItemFieldIterationValue {
title
field { ... on ProjectV2IterationField {name dataType project {url title}} }
}
# number
... on ProjectV2ItemFieldNumberValue {
number
field { ... on ProjectV2Field {name dataType project {url title}} }
}
# date
... on ProjectV2ItemFieldDateValue {
date
field { ... on ProjectV2Field {name dataType project {url title}} }
}
value
}
}
}
Expand Down
41 changes: 19 additions & 22 deletions src/Renderer/Library/GitHub/V4/GitHubV4ProjectNextClient.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {RemoteGitHubV4ProjectEntity, RemoteGitHubV4ProjectFieldEntity} from '../../Type/RemoteGitHubV4/RemoteGitHubV4ProjectEntity';
import {GitHubV4Client} from './GitHubV4Client';
import {RemoteGitHubV4ProjectNextEntity, RemoteGitHubV4ProjectNextFieldEntity} from '../../Type/RemoteGitHubV4/RemoteGitHubV4ProjectNextEntity';

export class GitHubV4ProjectNextClient extends GitHubV4Client {
async getProjectStatusFieldNames(projectUrl: string): Promise<{ error?: Error; iterationName?: string; statusNames?: string[] }> {
// fetch data
const query = this.buildQuery(projectUrl);
const {error, data} = await this.request<RemoteGitHubV4ProjectNextEntity>(query);
const {error, data} = await this.request<RemoteGitHubV4ProjectEntity>(query);
if (error != null) return {error};

// iteration
Expand All @@ -15,8 +15,7 @@ export class GitHubV4ProjectNextClient extends GitHubV4Client {
// status
const statusField = this.getStatusField(data);
if (statusField == null) return {statusNames: [], iterationName};
const settings = JSON.parse(statusField.settings) as { options: { name: string }[] };
const statusNames = settings.options.map(option => option.name);
const statusNames = statusField.options.map(option => option.name);

return {statusNames, iterationName};
}
Expand All @@ -30,40 +29,38 @@ export class GitHubV4ProjectNextClient extends GitHubV4Client {
.replace('__PROJECT_NUMBER__', projectNumber);
}

private getIterationField(data: RemoteGitHubV4ProjectNextEntity): RemoteGitHubV4ProjectNextFieldEntity | null {
if (data.user?.projectNext != null) {
return data.user.projectNext.fields.nodes.find(field => field.dataType === 'ITERATION');

} else if (data.organization?.projectNext != null) {
return data.organization.projectNext.fields.nodes.find(field => field.dataType === 'ITERATION');
private getIterationField(data: RemoteGitHubV4ProjectEntity): RemoteGitHubV4ProjectFieldEntity | null {
if (data.user?.projectV2 != null) {
return data.user.projectV2.fields.nodes.find(field => field.dataType === 'ITERATION');
} else if (data.organization?.projectV2 != null) {
return data.organization.projectV2.fields.nodes.find(field => field.dataType === 'ITERATION');
}
}

private getStatusField(data: RemoteGitHubV4ProjectNextEntity): RemoteGitHubV4ProjectNextFieldEntity | null {
if (data.user?.projectNext != null) {
return data.user.projectNext.fields.nodes.find(field => {
return field.dataType === 'SINGLE_SELECT' && field.name.toLocaleLowerCase() === 'status' && field.settings != null;
private getStatusField(data: RemoteGitHubV4ProjectEntity): RemoteGitHubV4ProjectFieldEntity | null {
if (data.user?.projectV2 != null) {
return data.user.projectV2.fields.nodes.find(field => {
return field.dataType === 'SINGLE_SELECT' && field.name.toLocaleLowerCase() === 'status' && field.options != null;
});

} else if (data.organization?.projectNext != null) {
return data.organization.projectNext.fields.nodes.find(field => {
return field.dataType === 'SINGLE_SELECT' && field.name.toLocaleLowerCase() === 'status' && field.settings != null;
} else if (data.organization?.projectV2 != null) {
return data.organization.projectV2.fields.nodes.find(field => {
return field.dataType === 'SINGLE_SELECT' && field.name.toLocaleLowerCase() === 'status' && field.options != null;
});
}
}
}

const queryTemplate = `
__QUERY_TYPE__(login: "__USER_NAME__") {
projectNext(number: __PROJECT_NUMBER__) {
projectV2(number: __PROJECT_NUMBER__) {
title
fields(first: 100) {
nodes {
dataType
name
settings
... on ProjectV2SingleSelectField { name dataType options { name } }
... on ProjectV2IterationField {name dataType }
}
}
}
}
`;
`;
4 changes: 2 additions & 2 deletions src/Renderer/Library/Type/RemoteGitHubV3/RemoteIssueEntity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {RemoteGitHubV4ProjectNextFieldValue} from '../RemoteGitHubV4/RemoteGitHubV4IssueNodesEntity';
import {RemoteGitHubV4ProjectFieldValue} from '../RemoteGitHubV4/RemoteGitHubV4IssueNodesEntity';

export type RemoteIssueEntity = {
id: number;
Expand Down Expand Up @@ -68,7 +68,7 @@ export type RemoteProjectFieldEntity = {
value: string;
projectTitle: string;
projectUrl: string;
dataType: RemoteGitHubV4ProjectNextFieldValue['projectField']['dataType'];
dataType: RemoteGitHubV4ProjectFieldValue['field']['dataType'];
}

export type RemoteReviewEntity = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export type RemoteGitHubV4IssueEntity = {
projectCards: {
nodes: RemoteGitHubV4ProjectCard[];
};
projectNextItems: {
nodes: RemoteGitHubV4ProjectNextItem[];
projectItems: {
nodes: RemoteGitHubV4ProjectItem[];
};
lastTimelineUser: string;
lastTimelineAt: string;
Expand Down Expand Up @@ -89,23 +89,32 @@ export type RemoteGitHubV4ProjectCard = {
};
}

export type RemoteGitHubV4ProjectNextItem = {
export type RemoteGitHubV4ProjectItem = {
fieldValues: {
nodes: RemoteGitHubV4ProjectNextFieldValue[];
nodes: RemoteGitHubV4ProjectFieldValue[];
}
}

export type RemoteGitHubV4ProjectNextFieldValue = {
projectField : {
export type RemoteGitHubV4ProjectFieldValue = {
field?: {
name: string;
settings: string;
dataType: 'TITLE' | 'SINGLE_SELECT' | 'ITERATION' | 'TEXT' | 'NUMBER' | 'DATE' | 'EXPANDED_ITERATION' | string;
project: {
title: string;
url: string;
}
};
value: string;
// SINGLE_SELECTの場合の選択肢一覧
options?: {name: string}[];
}
text?: string; // TITLE or TEXT
number?: number; // NUMBER
date?: string; // DATE
name?: string; // SINGLE_SELECT

// ITERATION
title?: string;
duration?: number;
startDate?: string;
}

export type RemoteGitHubV4Review = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {RemoteGitHubV4Entity} from './RemoteGitHubV4Entity';
import {RemoteGitHubV4ProjectFieldValue} from './RemoteGitHubV4IssueNodesEntity';

export type RemoteGitHubV4ProjectFieldEntity = Omit<RemoteGitHubV4ProjectFieldValue['field'], 'project'>;

export type RemoteGitHubV4ProjectEntity = RemoteGitHubV4Entity & {
user?: {
projectV2?: {
fields: {
nodes: RemoteGitHubV4ProjectFieldEntity[];
}
}
}
organization?: {
projectV2?: {
fields: {
nodes: RemoteGitHubV4ProjectFieldEntity[];
}
}
}
};

This file was deleted.

0 comments on commit a91e79a

Please sign in to comment.