ããã«ã¡ã¯ takayuki ã§ãã Google ããŒã¿ããŒã¿ã« ã¯ãGoogle ãæäŸãããµãŒãã¹ãäžå¿ã«æ§ã
ãªããŒã¿ãœãŒã¹ãå©çšããŠãããã·ã¥ããŒãã®æ§ç¯ãã§ããŸãã ããŒã¿ãœãŒã¹ãçšæãããŠããªããã®ã§ãèªåã§ã³ãã¯ã¿ãäœæããŠã Google ããŒã¿ããŒã¿ã«ã«ããŒã¿ãåã蟌ããŸããã³ãã¯ã¿ã¯ Google Apps Script ã§èšè¿°ãã Web APIã CSV ã JSON ã XML ãApps Script Services ã JDBC API ãªã©ããããŒã¿ãååŸã§ããŸãã ä»åã¯ã REST API ããããŒã¿ãåã蟌ãã³ãã¯ã¿ãäœããããšæããŸãã REST API ã®ãµãŒãã¹ãšããŠã Redmine ãããŒã¹ã«èª¬æããŠãããŸãã åºæ¬çã«ã©ã㪠REST API ã§ãæ§ç¯ã®ä»æ¹ã¯åæ§ã«ã§ãããšæããŸãã®ã§ãé©å®ãèªèº«ã®ãå©çšããããµãŒãã¹ã«çœ®ãæããŠãèªã¿ãã ããã ã¯ããã«ããŒã¿ããŒã¿ã«ã®å®æåœ¢ã®ã€ã¡ãŒãžããèŠããããšããã®ãããªåœ¢ã«ãªããŸãã WESEEK ã®éçºææ³ åŒç€Ÿã§ã¯ãã¢ãžã£ã€ã«éçºã®ç®¡çããŒã«ãšããŠããã® Redmine ã䜿çšããŠããŸãã sprint ããšã«ã©ããããé床ïŒããã·ãã£ïŒãåºããŠããããããããžã§ã¯ãã«ãã£ãŠã¯ããæéã«ãããŠã©ããããã¹ããŒãªãŒãã€ã³ããæ¶åããŠãããã¯æ°ã«ãªããšããã§ãã Redmine ã® REST API ãå©çšããŠå®çžŸæ
å ±ãååŸããããŒã¿ããŒã¿ã«ã§é²èЧã§ããããã«ãªã£ãã䟿å©ã ãªãšããã®ãããã®ã³ãã¯ã¿ãŒäœæã®ãã£ããã§ãã Redmine ã® API ã䜿ã API ã®æå¹å 管ç > èªèšŒ > RESTã«ããWebãµãŒãã¹ãæå¹ã«ãã ã«ãã§ãã¯ãå
¥ããŠã Redmine ã§ REST API ãå©çšã§ããããã«ããŸãã API ã¢ã¯ã»ã¹ããŒã®ç¢ºèª Redmine REST API ãåŒã³åºããšãã¯ã API ã¢ã¯ã»ã¹ããŒãå¿
èŠã§ãã å人èšå® > APIã¢ã¯ã»ã¹ã㌠ãã確èªããŠãããŸãã ãã±ããäžèЧã®ååŸ è©Šãã« REST API ãå©çšããŠããã±ããäžèЧãååŸããŠã¿ãŸãã HTTP Header ã« X-Redmine-API-Key ã远å ãã API ã¢ã¯ã»ã¹ããŒãã»ããããŸãã curl ã§åŒã³åºããŠã¿ããšãäžèšã®ããã«ãã±ããäžèЧãååŸã§ããŸããã $ curl -s --request GET 'https://[REDMINE HOSTNAME]/issues.json?project_id=1&limit=2' --header 'X-Redmine-API-Key: [REDMINE API TOKEN]' | jq . { "issues": [ { "id": 3503, "project": { "id": 1, "name": "[FILTERED]" }, "tracker": { "id": 5, "name": "ã¿ã¹ã¯" }, "status": { "id": 1, "name": "æ°èŠ" }, "priority": { "id": 2, "name": "éåžž" }, "author": { "id": 141, "name": "[FILTERED]" }, "fixed_version": { "id": 943, "name": "sprint-25" }, "parent": { "id": 103421 }, "subject": "åäœç¢ºèª", "description": "", "start_date": "2022-05-19", "done_ratio": 0, "created_on": "2022-08-29T07:36:06Z", "updated_on": "2022-08-29T07:36:06Z" }, { "id": 3502, "project": { "id": 1, "name": "[FILTERED]" }, "tracker": { "id": 5, "name": "ã¿ã¹ã¯" }, "status": { "id": 1, "name": "æ°èŠ" }, "priority": { "id": 2, "name": "éåžž" }, "author": { "id": 141, "name": "[FILTERED]" }, "fixed_version": { "id": 943, "name": "sprint-25" }, "parent": { "id": 103421 }, "subject": "PR & Merge #3240", "description": "PR\r\nã¿ã¹ã¯\r\n#3240", "start_date": "2022-05-19", "done_ratio": 0, "created_on": "2022-08-29T07:35:58Z", "updated_on": "2022-08-29T07:35:58Z" } ], "total_count": 82, "offset": 0, "limit": 2 } Redmine REST API ã®è©³çŽ°ã¯ ãã¡ã ãåç
§ããŠãã ããã ããŒã¿ããŒã¿ã« ã³ãã¥ãã㣠ã³ãã¯ã¿ãäœã Google ããŒã¿ããŒã¿ã«ã® Codelab ãåèã«æ§ç¯ããŠãããŸãã Apps Script ã®ãããžã§ã¯ããäœããäžèšã®4ã€ã®é¢æ°ãå®çŸ©ããŠãã³ãã¯ã¿ãäœæããŠãããŸãã getAuthType() getConfig() getSchema() getData() Apps Script ãããžã§ã¯ããäœã Google Apps Script ã«ã¢ã¯ã»ã¹ããŸãã æ°ãããããžã§ã¯ã ãã¯ãªãã¯ããŸãã ã¯ã©ã·ã㯠ãšãã£ã¿ã䜿çšãã ãã¯ãªãã¯ããŸãã ã¯ã©ã·ã㯠ãšãã£ã¿ã¯ 2022 幎 9 æãŸã§ã«å»æ¢ãããäºå®ã§ããã Codelab ã§ã¯ãŸã ã¯ã©ã·ã㯠ãšãã£ã¿ ã察象ãšããå
容ãšãªã£ãŠããããããã¡ãã§è§£èª¬ããŸããæ°ãããšãã£ã¿ã§ã®æ§ç¯æ¹æ³ãå
¬éãããŸããããå
容ãã¢ããããŒãããŸãã ããããã ã³ãŒã.gs ã®äžã«æ©èœãå®è£
ããŠãããŸãã æäžéšãã¯ãªãã¯ããŠããã®ãããžã§ã¯ãã«ååãã€ããŸãã ä»å㯠RedmineDataStudioConnector ãšããŸããã getAuthType() ã®å®çŸ© ã³ãŒããå
ã«ç€ºããŸãã var cc = DataStudioApp.createCommunityConnector(); function getAuthType() { var AuthTypes = cc.AuthType; return cc .newAuthTypeResponse() .setAuthType(AuthTypes.NONE) .build(); } getAuthType() ã¯ã ããŒã¿ããŒã¿ã«ãã³ãã¯ã¿ã䜿çšããèªèšŒæ¹æ³ãç¥ãå¿
èŠããããšãã«åŒã³åºãããŸãã èªèšŒæ¹æ³ã¯äžèšããããŸãã åæå€ 説æ NONE ã³ãã¯ã¿ã«èªèšŒãå¿
èŠãªãããšã瀺ããŸãã OAUTH2 ã³ãã¯ã¿ãèªèšŒã« OAuth 2.0 ã䜿çšããããšã瀺ããŸãã KEY ã³ãã¯ã¿ãèªèšŒã« API ããŒã䜿çšããããšã瀺ããŸãã USER_PASS ã³ãã¯ã¿ãèªèšŒã«ãŠãŒã¶ãŒåãšãã¹ã¯ãŒãã䜿çšããããšã瀺ããŸãã USER_TOKEN ã³ãã¯ã¿ãèªèšŒã«ãŠãŒã¶ãŒåãšããŒã¯ã³ã䜿çšããããšã瀺ããŸãã äœæããã³ãã¯ã¿ãäžè¬ã«ãå
¬éããå Žåã«ã¯ãé©åãªèªèšŒæ¹æ³ãéžæããããšãå¿
èŠã§ããä»å㯠Codelab ã§ã説æããŠãã NONE ãå©çšããŸãã èªèšŒæ¹æ³ã®è©³çŽ°ã¯ ãã¡ã ãåç
§ããŠãã ããã getConfig() ã®å®çŸ© ããŒã¿ããŒã¿ã«ã§äœæããã³ãã¯ã¿ãããŒã¿ãœãŒã¹ãšããŠè¿œå ããéã«ãã³ãã¯ã¿ã«ä»»æã®ãã©ã¡ãŒã¿ãæž¡ãããã«ã§ããŸãã ãã®ãããªãã®ã§ãã ã³ãŒããäžèšã«ç€ºããŸãã getAuthType() ã®åŸã«ç¶ããŠèšèŒããŠãã ããã function getConfig(request) { var config = cc.getConfig(); config.newInfo() .setId('instructions') .setText('Enter the Redmine project ID to get a list of Issues for the project.'); config.newTextInput() .setId('project') .setName('Enter a project id'); config.setDateRangeRequired(true); return config.build(); } config.newInfo() ã§ã¯ããŠãŒã¶ãŒã«æç€ºãæ
å ±ãæäŸããããã®ããã¹ããå®çŸ©ããŠããŸãã config.newTextInput() ã§ã¯ã 1 è¡ã®ããã¹ãããã¯ã¹ãå®çŸ©ããŠããŸããä»åã¯ã Redmine ã® ãããžã§ã¯ã ID ã®å
¥åãä¿ããŠããŸãã 䜿çšã§ãã ConfigType ã®äžèŠ§ã¯ ãã¡ã ãåç
§ããŠãã ããã getSchema() ã®å®çŸ© getSchema() ã¯ã ããŒã¿ããŒã¿ã«ã«ããŒã¿ãèªã¿èŸŒãéã®ã¹ããŒããå®çŸ©ããŸãã ãã®é¢æ°ãè¿ããçµæã¯ãããŒã¿ããŒã¿ã«ã®ããŒã¿ãœãŒã¹ã®ç·šéã§è¡šç€ºããããã£ãŒã«ãäžèЧã«å¯Ÿå¿ããŸãã ã³ãŒãã瀺ããŸãã function getFields(request) { var cc = DataStudioApp.createCommunityConnector(); var fields = cc.getFields(); var types = cc.FieldType; var aggregations = cc.AggregationType; // ããŒã¿ããŒã¿ã«äžã§ `id` ãšäžæã«èšå®ãã衚瀺åã `ID` ã«ããã®ããŒã¿åã `NUMBER` åã«èšå®ããŠããŸã fields.newDimension() .setId('id') .setName('ID') .setType(types.NUMBER); fields.newDimension() .setId('projectId') .setName('ãããžã§ã¯ã ID') .setType(types.NUMBER); fields.newDimension() .setId('project') .setName('ãããžã§ã¯ã') .setType(types.TEXT); fields.newDimension() .setId('trackerId') .setName('ãã©ãã«ãŒ ID') .setType(types.NUMBER); fields.newDimension() .setId('tracker') .setName('ãã©ãã«ãŒ') .setType(types.TEXT); fields.newDimension() .setId('statusId') .setName('ã¹ããŒã¿ã¹ ID') .setType(types.NUMBER); fields.newDimension() .setId('status') .setName('ã¹ããŒã¿ã¹') .setType(types.TEXT); fields.newDimension() .setId('priorityId') .setName('åªå
床 ID') .setType(types.NUMBER); fields.newDimension() .setId('priority') .setName('åªå
床') .setType(types.TEXT); fields.newDimension() .setId('authorId') .setName('äœæè
ID') .setType(types.NUMBER); fields.newDimension() .setId('author') .setName('äœæè
') .setType(types.TEXT); fields.newDimension() .setId('assignedToId') .setName('æ
åœè
ID') .setType(types.NUMBER); fields.newDimension() .setId('assignedTo') .setName('æ
åœè
') .setType(types.TEXT); fields.newDimension() .setId('fixedVersionId') .setName('察象ããŒãžã§ã³ ID') .setType(types.NUMBER); fields.newDimension() .setId('fixedVersion') .setName('察象ããŒãžã§ã³') .setType(types.TEXT); fields.newDimension() .setId('parent') .setName('芪ãã±ãã ID') .setType(types.NUMBER); fields.newDimension() .setId('subject') .setName('é¡å') .setType(types.TEXT); fields.newDimension() .setId('description') .setName('説æ') .setType(types.TEXT); fields.newDimension() .setId('startDate') .setName('éå§æ¥') .setType(types.YEAR_MONTH_DAY); fields.newDimension() .setId('createdOn') .setName('äœææ¥') .setType(types.YEAR_MONTH_DAY_SECOND); fields.newMetric() .setId('doneRatio') .setName('鲿ç') .setType(types.PERCENT) .setAggregation(aggregations.SUM); fields.newDimension() .setId('updatedOn') .setName('æŽæ°æ¥') .setType(types.YEAR_MONTH_DAY_SECOND); fields.newMetric() .setId('storyPoints') .setName('ã¹ããŒãªãŒãã€ã³ã') .setType(types.NUMBER) .setAggregation(aggregations.SUM); return fields; } function getSchema(request) { var fields = getFields(request).build(); return { schema: fields }; } ãã±ããäžèЧã®ååŸ ã§ååŸããå
容ãããšã«ãã¹ããŒããå®çŸ©ããŠããŸãã ããŒã¿ããŒã¿ã«äžã§ããã£ã¡ã³ã·ã§ã³ãšããŠå®çŸ©ãããå Žå㯠newDimension() ãã ææšãšããŠå®çŸ©ãããå Žå㯠newMetric() ãå®çŸ©ããŸãã setId() ã¯ãäžæã®ããŒãå®çŸ©ããŸããããã¯åŸè¿°ãã getData() ã§ã REST API ããååŸããçµæãããŒã¹ãããšãã«å¿
èŠãšãªããŸãã getName() ã¯ãã£ãŒã«ãäžèЧã«è¡šç€ºããããã£ãŒã«ãåãå®çŸ©ããŸãã setType() ã¯ãã£ãŒã«ãã®ããŒã¿åãæå®ããŸãã 䜿çšã§ããããŒã¿å㯠ãã¡ã ãåç
§ããŠãã ããã getData() ã®å®çŸ© ããŒã¿ããŒã¿ã«äžã§äžèšã®ã€ãã³ããçºçãããšãã³ãã¯ã¿ã® getData() ãåŒã³åºãããŸãã ãŠãŒã¶ãŒãããã·ã¥ããŒãã«ã°ã©ãã远å ãããšã ãŠãŒã¶ãŒãã°ã©ããç·šéãããšã ãŠãŒã¶ãŒãããã·ã¥ããŒãã衚瀺ãããšã ãŠãŒã¶ãŒãé¢é£ä»ãããããã£ã«ã¿ãŸãã¯ããŒã¿ ã³ã³ãããŒã«ãç·šéãããšã ããŒã¿ããŒã¿ã«ãããŒã¿ãµã³ãã«ãå¿
èŠãšãããšã getData() ã¯ãã®ååã®éãã察象ãšããããŒã¿ããŒã¹ïŒä»å㯠Redmine REST APIïŒããå®éã«ããŒã¿ãååŸããå®çŸ©ãããã¹ããŒãã«ããŒã¹ããŠããŒã¿ããŒã¿ã«ã«è¿ãåŠçãè¡ããŸãã å
ã«ã³ãŒãã瀺ããŸãã function getData(request) { // åŸè¿°ã®ãèŠæ±ããããã£ãŒã«ãã®ã¹ããŒããäœæãããã§è©³ãã説æããŸã var requestedFieldIds = request.fields.map(function(field) { return field.name; }); var requestedFields = getFields().forIds(requestedFieldIds); // åŸè¿°ã® ãAPI ããããŒã¿ãååŸããŠè§£æãããã§è©³ãã説æããŸã // Fetch and parse data from API var url = [ 'https://[REDMINE HOSTNAME]/issues.json?', 'project_id=', request.configParams.project, '&limit=100' ]; var headers = { 'X-Redmine-API-Key': PropertiesService.getScriptProperties().getProperty("REDMINE_API_KEY") }; var options = { "headers": headers }; var response = UrlFetchApp.fetch(url.join(''), options); var parsedResponse = JSON.parse(response).issues; var rows = responseToRows(requestedFields, parsedResponse); return { schema: requestedFields.build(), rows: rows }; } // åŸè¿°ã®ãè§£æãããããŒã¿ã倿ããèŠæ±ããããã£ãŒã«ãããã£ã«ã¿ãããã§è©³ãã説æããŸã function responseToRows(requestedFields, response) { // Transform parsed data and filter for requested fields return response.map(function(issue) { var row = []; requestedFields.asArray().forEach(function (field) { switch (field.getId()) { case 'id': return row.push(issue.id); case 'projectId': return row.push(issue.project.id); case 'project': return row.push(issue.project.name); case 'trackerId': return row.push(issue.tracker.id); case 'tracker': return row.push(issue.tracker.name); case 'statusId': return row.push(issue.status.id); case 'status': return row.push(issue.status.name); case 'priorityId': return row.push(issue.priority.id); case 'priority': return row.push(issue.priority.name); case 'authorId': return row.push(issue.author.id); case 'author': return row.push(issue.author.name); case 'assignedToId': // ã¬ã¹ãã³ã¹ã® issue.assigned_to ã®ããŒããªããã° null ããããã§ãªããã° id ãè¿ããŸã return row.push(issue.assigned_to && issue.assigned_to.id); case 'assignedTo': return row.push(issue.assigned_to && issue.assigned_to.name); case 'fixedVersionId': return row.push(issue.fixed_version && issue.fixed_version.id); case 'fixedVersion': return row.push(issue.fixed_version && issue.fixed_version.name); case 'parentId': return row.push(issue.parent && issue.parent.id); case 'subject': return row.push(issue.subject); case 'description': return row.push(issue.description); case 'startDate': // ã¬ã¹ãã³ã¹ã® issue.start_date ã®ããŒããªããã° null ããããã§ãªããã° 2022-08-29 ãšãã圢åŒã®æ¥ä»ã 20220829 ã«å€æããŠå€ãè¿ããŸã return row.push(issue.start_date && issue.start_date.replace(/-/g, '')); case 'doneRatio': return row.push(issue.done_ratio/100); case 'createdOn': return row.push(issue.created_on && issue.created_on.replace(/-|:|T|Z/g, '')); case 'updatedOn': return row.push(issue.updated_on && issue.updated_on.replace(/-|:|T|Z/g, '')); case 'storyPoints': return row.push(issue.story_points); default: return row.push(''); } }); return { values: row }; }); } request ãªããžã§ã¯ã getData() ã§åç
§ãããŠãã request ãªããžã§ã¯ãã«ã€ããŠèª¬æããŸãã ããŒã¿ããŒã¿ã«ããã³ãã¯ã¿ã® getData() ãåŒã³åºããããšããã® request ãªããžã§ã¯ããæž¡ãããŸãã request ãªããžã§ã¯ãã¯äžèšã®ãããªæ§é ã«ãªã£ãŠããŸãã { configParams: object, scriptParams: object, dateRange: { startDate: string, endDate: string }, fields: [ { name: Field.name } ] } äŸãã°ã configParams ã¯ã getConfig() ã®å®çŸ© ã§èª¬æãããã£ãŒã«ãã«å
¥åããå
å®¹ãæ ŒçŽãããŸãããã®ãããªæ§é ã§ãã { configParams: { project: '1' }, ... } ã³ãã¯ã¿ã® getData() ãå®è£
ããéã¯ã request ãªããžã§ã¯ãã®äžèº«ãé©åã«åŠçããŠããŒã¿ãè¿ããŸãã request ãªããžã§ã¯ãã®è©³çŽ°ã¯ ãã¡ã ãåç
§ããŠãã ããã èŠæ±ããããã£ãŒã«ãã®ã¹ããŒããäœæãã request ãªããžã§ã¯ãã® fields ãããããŒã¿ããŒã¿ã«ãããªã¯ãšã¹ãããããã£ãŒã«ãã®ã¹ããŒããäœæããŸããã³ãã¯ã¿ã¯APIã«åãåãããããŒã¿ã®ãã¡ããããã®ãã£ãŒã«ãã«ãã£ã«ã¿ããŠãçµæãããŒã¿ããŒã¿ã«ã«è¿ãããã«ããŸãã var requestedFieldIds = request.fields.map(function(field) { return field.name; }); var requestedFields = getFields().forIds(requestedFieldIds); API ããããŒã¿ãååŸããŠè§£æãã å®éã« API ã«åãåãããŠããŒã¿ãååŸããŸãã ãã±ããäžèЧã®ååŸ ã§ Redmine REST API ãåŒã³åºããã®ãšåæ§ã®ãšã³ããã€ã³ãã url ã«å®çŸ©ããŸãã project_id ã«ã¯ã request ãªããžã§ã¯ãã®äžã«ãã configParams.project ãæž¡ããŠããŸããããã¯ããŒã¿ããŒã¿ã«ã®ããŒã¿ãœãŒã¹ã®èšå®æã«å
¥åããããããžã§ã¯ã ID ã®å€ã§ãã Redmine REST API ã¯ã 1 åã®ãªã¯ãšã¹ãã§ 100 ä»¶ãŸã§ã®ããŒã¿ããååŸã§ããŸããã 100 ä»¶ãè¶
ããããŒã¿ã«å¯Ÿå¿ããããã«ã¯ãããŒãžãååž°çã«è¿œããããå®è£
ãå¿
èŠã§ãã ä»åã¯ãã·ã³ãã«ã«ããããã«ãã®å®è£
ã岿ããŠããŸãã // Fetch and parse data from API var url = [ 'https://[REDMINE HOSTNAME]/issues.json?', 'project_id=', request.configParams.project, '&limit=100' ]; Redmine REST API ãåŒã³åºãéã«ã¯ã X-Redmine-API-Key ããããŒã®æå®ãå¿
èŠã§ãããããã¯ç§å¿æ
å ±ãªãããã³ãŒãäžã«çŽæ¥èšèŒããã®ã¯æãŸãããããŸããã Google Apps Script ã®ãããã㣠ãµãŒãã¹ã䜿çšããŠãç§å¿æ
å ±ã管çããŸãã ãã¡ã€ã« > ãããžã§ã¯ãã®ãããã㣠ãéããŸãã ã¹ã¯ãªããã®ãããã㣠ã¿ããã¯ãªãã¯ããäžèšã®ããã«è¿œå ããŸãã ãããã㣠ã«ã¯ REDMINE_API_KEY ãã å€ ã«ã¯ Redmine API ã¢ã¯ã»ã¹ã㌠ãå
¥åããŸãã äžèšã®ããã«ã PropertiesService.getScriptProperties().getProperty() ã䜿çšããŠãå®çŸ©ããããããã£ãåç
§ã§ããŸãã var headers = { 'X-Redmine-API-Key': PropertiesService.getScriptProperties().getProperty("REDMINE_API_KEY") }; var options = { "headers": headers }; var response = UrlFetchApp.fetch(url.join(''), options); var parsedResponse = JSON.parse(response).issues; è§£æãããããŒã¿ã倿ããèŠæ±ããããã£ãŒã«ãããã£ã«ã¿ãã switch case æã䜿ãããªã¯ãšã¹ãããããã£ãŒã«ãã®çµæãè¿ãããã«ããŒã¹ããŠãããŸãã case ã«èšèŒããŠããããŒã¯ã getSchema() ã§ setId() ããå€ãæå®ããŸãã function responseToRows(requestedFields, response) { // Transform parsed data and filter for requested fields return response.map(function(issue) { var row = []; requestedFields.asArray().forEach(function (field) { switch (field.getId()) { case 'id': return row.push(issue.id); case 'projectId': return row.push(issue.project.id); ... default: return row.push(''); } }); return { values: row }; }); } REST API ã®çµæã«ããŒãå«ãŸããªãå Žåãããå Žåã¯ãäžèšã®ããã«ã㊠null safe ã«ããŸãã case 'assignedToId': return row.push(issue.assigned_to && issue.assigned_to.id); REST API ã®çµæãããŒã¿ããŒã¿ã«ã®ããŒã¿åã«å€æããããšãå¿
èŠã«ãªãå ŽåããããŸãã createdOn ã¯ã 2022-08-29T07:35:58Z ã®ãããªåœ¢ã§ REST API ããè¿ãããŸãããããŒã¿ããŒã¿ã«äžã§ã¯ 20220829073558 ã®ãããªåœ¢åŒã®ããŒã¿ãå¿
èŠã§ãã äžèšã®ããã«ããŠãããŒã¿ã®å€æãè¡ããŸãã case 'createdOn': return row.push(issue.created_on && issue.created_on.replace(/-|:|T|Z/g, '')); ãããã§ã¹ãã®äœæ ãããã§ã¹ããäœæããŠãããŒã¿ããŒã¿ã«ããã³ãã¯ã¿ã远å ããéã®ã³ãã¯ã¿ã®æ
å ±ãå®çŸ©ããŸãã 衚瀺 > ãããã§ã¹ã ãã¡ã€ã«ã衚瀺 ãã¯ãªãã¯ããŸãã ãã¡ã€ã«ã« application.json ã衚瀺ãããããã«ãªããŸããã application.json ãäžèšã®ããã«æžãå€ããŸãã { "timeZone": "Asia/Tokyo", "dependencies": { }, "runtimeVersion": "DEPRECATED_ES5", "dataStudio": { "name": "Redmine", "logoUrl": "https://www.redmine.org/attachments/download/3462/redmine_fluid_icon.png", "company": "WESEEK, Inc.", "companyUrl": "https://weseek.co.jp/", "addonUrl": "https://weseek.co.jp/", "supportUrl": "https://weseek.co.jp/", "description": "Get a list of Issues in a Redmine project.", "sources": ["redmine"] } } dataStudio object ã«ã€ããŠèª¬æããŸãããããã¯ãããŒã¿ããŒã¿ã«ã§ããŒã¿ãœãŒã¹ã远å ããéã«è¡šç€ºãããæ
å ±ã§ãã name ã¯ãã³ãã¯ã¿ã®ååã§ãã logoUrl ã¯ã¢ã€ã³ã³ç»åã§ãããã§ã¯ Redmine ã®ããŽãèšå®ããŸããã company companyUrl ã¯ãã®ã³ãã¯ã¿ã補äœããçµç¹ã®æ
å ±ãå
¥åããŸãã addonUrl ã¯ããã®ã³ãã¯ã¿ã®å°çšã®è©³çްããŒãžã® URL ãèšèŒããŸããä»åã¯äœæããŠããªãããã companyUrl ãšåãã«ããŠããŸãã supportUrl ã¯ããã®ã³ãã¯ã¿ã®ãµããŒãããŒãžã® URL ãèšèŒããŸããä»åã¯äœæããŠããªãããã companyUrl ãšåãã«ããŠããŸãã description ã«ã¯ãã³ãã¯ã¿ã®èª¬æãèšèŒããŸãã sources ã«ã¯ããã®ã³ãã¯ã¿ãå©çšã§ããããŒã¿ãœãŒã¹ã®ãªã¹ããåæããŸããä»åã¯ã redmine ãšã®ã¿èšèŒããŠããŸãã ãããã§ã¹ãã®è©³çŽ°ã¯ ãã¡ã ãåç
§ããŠãã ããã ã³ãã¯ã¿ããããã€ãã å
¬é > ãããã§ã¹ãããé
眮 ãã¯ãªãã¯ããŸãã Get ID ãã¯ãªãã¯ãã衚瀺ããã Deployment ID ãã³ããŒããŠãããŸãã ããŒã¿ããŒã¿ã«ããã³ãã¯ã¿ãå©çšãã äœæããã³ãã¯ã¿ã«æ¥ç¶ãã ããŒã¿ããŒã¿ã« ãéãã äœæ > ã¬ããŒã ãã¯ãªãã¯ããŠãæ°ããã¬ããŒããäœæããŸãã ããŒã¿ã®ã¬ããŒããžã®è¿œå ã§ã ç¬èªã«äœæ ãã¯ãªãã¯ããŸãã Deployment ID ã«ãå
ã»ã©ã³ããŒãã Deployment ID ãå
¥åããæ€èšŒãã¯ãªãã¯ããŸãã äžéšã«ãã®ããã«è¡šç€ºããããã Redmine ã®æ ãã¯ãªãã¯ããŸãã ããŒã¿ããŒã¿ã«ã«è¡šç€ºããã Redmine ãããžã§ã¯ãã® ID ãå
¥åãã远å ãã¯ãªãã¯ããŸãã ã°ã©ãã远å ããŠããŒã¿ã衚瀺ããŠã¿ã ããã§ã¯ãäŸãšã㊠Redmine ããååŸããããŒã¿ãå
ã«è¡šãæç»ããŠã¿ãŸãã ã°ã©ãã远å > 衚 ãã¯ãªãã¯ããä»»æã®å Žæã«é
眮ããŸãã ããããäžèšã®ããã«éžæããŸãã ããŒã¿ãœãŒã¹ Redmine ãã£ã¡ã³ã·ã§ã³ é¡å 察象ããŒãžã§ã³ ã¹ããŒã¿ã¹ äœææ¥ ææš SUM: ã¹ããŒãªãŒãã€ã³ã ãã®ãããªè¡šãæ§ç¯ã§ããŸãã ãã®ä»ã«æ£ã°ã©ããããŠã©ãŒã¿ãŒãã©ãŒã«ã°ã©ããªã©ã䜿çšããŠããã®ãããªããã·ã¥ããŒããæ§ç¯ã§ããŸãã