å
容
èæ¯
ã¹ããŒãã§è£çŠãªäººã®ããã®éžæè¢
å¿ èŠãªäºçŽ
Jira REST APIã®äœ¿çšãéå§ãã
Qt Creatorã§ãããžã§ã¯ããäœæãã
ãªã¯ãšã¹ãã«ãŒãã®ãã¶ã€ã³ãæã
ã«ãŒãã®åã«ã€ããŠèª¬æããŸã
ããŒããŠã£ã³ããŠ
REST APIãåŒã³åºãã³ãŒããæžã
ãã©ã¡ãŒã¿ãŒãä¿åããã³åŸ©å ããããã®LocalStorage
ã°ã«ãŒãåãªãã·ã§ã³ãè¿œå ãã
次ã¯ïŒ
ã¹ããŒãã§è£çŠãªäººã®ããã®éžæè¢
å¿ èŠãªäºçŽ
Jira REST APIã®äœ¿çšãéå§ãã
Qt Creatorã§ãããžã§ã¯ããäœæãã
ãªã¯ãšã¹ãã«ãŒãã®ãã¶ã€ã³ãæã
ã«ãŒãã®åã«ã€ããŠèª¬æããŸã
ããŒããŠã£ã³ããŠ
REST APIãåŒã³åºãã³ãŒããæžã
ãã©ã¡ãŒã¿ãŒãä¿åããã³åŸ©å ããããã®LocalStorage
ã°ã«ãŒãåãªãã·ã§ã³ãè¿œå ãã
次ã¯ïŒ
èæ¯
å°ãåãä»ã¯ã»ãŒå¥ã®äººçãæ©ãã§ããŠãç§ããããžã§ã¯ããããŒãžã£ãŒã ã£ããšãããããžã§ã¯ãåå è ã®éçšã«ã€ããŠã®èãã倱ã£ãŠããããšã«æ°ä»ããŸããã 誰ãã倧èŠæš¡ã§éèŠãªããžãã¹ã«åŸäºããŠããã誰ããç·æ¥ã®ãã°ãä¿®æ£ããŠããããããã¯èª°ããç§ã倱瀌ããŠããã®ã«ãééã蹎ã£ãŠããã®ãããããŸããããç§ã¯ããã«ã€ããŠç¥ããŸããã ãããŠãç§ã¯æäºåé¡ãæ確ã«ææ¡ãããã£ãã®ã§ãã
çµç¹ããã§ã«æ ¢æ§æã«ããã°ããšèšºæãããŠãããèªç¶ã§å ·äœçãªãã®ãã¹ãŠã«åŒãå¯ããããŠããå ŽåãããããããŒãã¯æ¬¡ã®ããã«èŠããããã»ã¹ããšã«åé¢ãããŠããŸãïŒ
ããããæ®åœ±ã
ç§ã®å Žåããã®ãªãã·ã§ã³ã¯ããã€ãã®çç±ã§æ©èœããŸããã
ãŸããããŒã å šäœã¯ãæ°äººãé€ããŠå¥ã®éœåžã«ããŠããããªäŒè°ãæé ããããšã¯åççã§ã¯ãªãããã§ããã
第äºã«ãç§ã¯ãã¹ãŠã®èäœåŽåã«å·versionã«å«æªããçŽçãããŒãã«æåã§åºå®ãïŒä»ã®äººã¯ããŸããã§ãããåã®æ®µèœãåç §ïŒããã©ãã«ãŒã§ã¿ã¹ã¯ã®åãã远跡ããããã«å¿ããŠããŒãäžã®çŽçã移åããŸãã ã³ã³ãã¥ãŒã¿ãŒãExcelããŸãã¯Trelloã§ã«ãŒããåŒãããšãã§ããŸãããã¿ã¹ã¯ã«åŸã£ãŠèªåã§ã«ãŒããå床移åããå¿ èŠããããŸãã
第äžã«ããããŠæãéèŠãªããšãšããŠããã®ããŒããèŠããšãäžè¬çãªç¶æ³ã確èªãããœãããŠã§ã¢çç£ãã€ãã©ã€ã³ã®ã»ã¯ã·ã§ã³ã®ããã«ããã¯ãèŠã€ããããšãã§ããŸããã人ãšãã®è² è·ã¯èŠããŸããã
ã ããç§ã¯ããŒããå¿ èŠã§ããïŒ
aïŒé»å
bïŒãã©ãã«ãŒã«é¢é£ä»ããããŠãããã€ãŸã çŸåšã®ç¶æ³ãåæ
cïŒãããŠãããŒãäžã®åãç¹å®ã®äººã«å¯Ÿå¿ããããã«
èŠããã«ãç§ã¯ãã®æã«ãã®åé¡ã解決ãããŠã§ãããŒãžã§ãã¬ãŒã³ããŒã·ã§ã³ãè¡ããŸããã ããããããã«ã€ããŠã¯äœãã話ãããŸããããã©ãã«ãŒïŒPVCSãã©ãã«ãŒïŒã¯ããŸãæ®åããŠããããAPIã¯DLLäžã«ãããããŒãžã³ãŒãã¯èŠã€ãããŸããã
ãããŠä»ãç§ã¯QMLãããŒã«ããããšããŠãæŒç¿ãç¹°ãè¿ãããšã«ããŸããã éžæã¯ç°¡åã«èª¬æãããŠããŸã-Webãã¯ãããžãŒãããå°ã銎æã¿ããããPythonãšPyQtã§æžãããããŒã«ã«çµæã®ã¢ãžã¥ãŒã«ãåã蟌ãæ¹æ³ãç¥ã£ãŠããŸãã
ã¹ããŒãã§è£çŠãªäººã®ããã®éžæè¢
ã¯ããJiraã«ã¯ãããã°ãããŒããæã€ãã©ã°ã€ã³ãäžå®æ°ããããšãç¥ã£ãŠããŸã- åžå Žã§ãããã°ãããšããåèªãæ€çŽ¢ãããš33ã®ãªãã·ã§ã³ãèŠã€ãããŸãã
ãããããã©ã°ã€ã³ã䜿çšããã«ã¯ãJiraäžã®ãã¹ãŠã®ãŠãŒã¶ãŒã®æ°ã«å¯Ÿå¿ããäŸ¡æ Œã§ç®¡çããè³Œå ¥ãããã¯ã¢ãŠãããå¿ èŠãããããšãæå³ãããµãŒããŒã«ã€ã³ã¹ããŒã«ããŠãµããŒããããšãã管çè ã«åæããŸã....ç§ã®ããŒãºã«åãããŠã«ã¹ã¿ãã€ãºããããšã¯äžå¯èœã§ã ãã©ã°ã€ã³ã¯ãã¹ãŠå ±æãããŸãã ãããŠããµãŒããŒã«äœããã€ã³ã¹ããŒã«ãããŠãããã©ããã«é¢ä¿ãªã䜿çšã§ããããŒã«ãæã«å ¥ãã誰ãæ¯ãè¿ããã«ãããå€æŽãããã£ãã®ã§ãã
å¿ èŠãªäºçŽ
èšäºã«è² æ ããããªãããã«ãããã§ã¯ãã®æ¹æ³ã説æããŸããã
-Jiraã§ã®æ¿èª
-JIRAãžã®ã³ãŒã«è»¢éã䜿çšããQMLã®ã«ãŒãã§ã®æäœ-ãã©ãã°ã¢ã³ããããããªã©ã«ããã¹ããŒã¿ã¹ããã³ã¢ãŒãã£ã¹ãã®ç·šéãå€æŽ
-Jiraãã£ã«ã¿ãŒã䜿çšãã
ããã®ã©ãããããªãã«ãšã£ãŠæ¬åœã«èå³æ·±ããªããã³ã¡ã³ãã§ããã«ã€ããŠæžããŠãã ããã ç§ã¯ããã«ãããå®è¡ããŠè©³çŽ°ã«çœ²åãããšçŽæããŸãããã nmivanã èšã£ãããã« ããç§ã¯ãããèšç»ã«å ¥ããŸããã
çšèªã¯ãŸã 確ç«ãããŠããªãã®ã§ãäžéšã®äŒæ¥ã§ã¯èª²é¡ãšåŒã°ããä»ã®èª²é¡ã§ã¯ã¿ã¹ã¯ãšåŒã°ãã ãã±ãããšã¢ããªã±ãŒã·ã§ã³ãæ®ã£ãŠããŸã ã Jiraã§åé¡ãéžæãããŠãããã£ã«ã¿ãŒãšã³ãã£ãã£ã«ã¯ã filter ã query ã selection ã listãšããååã®æããããŸã ã
ããŒã«ã©ã€ãºãããJiraã§æ¡çšãããŠããçšèªã䜿çšããŸãïŒ åé¡ ã¯ãšãªãåŒã³åºãã ãªã¹ãããã£ã«ã¿ãŒã ãŸã ã
Jira REST APIã®äœ¿çšãéå§ãã
Jira Webã€ã³ã¿ãŒãã§ã€ã¹ã®å žåçãªãªã¯ãšã¹ãã¢ãã¬ã¹ã¯æ¬¡ã®ããã«ãªããŸãã
https://jira.mycompany.ru/browse/PROJECT-1234
ãããã³ã«ãšãã¹ãåãååŸããŸããã€ãŸãã
browse
ããã¢ãã¬ã¹ã®å é ãã
rest/api/2/
ãè¿œå ããREST APIã¢ãã¬ã¹ã®åºæ¬éšåãååŸããŸãã
https://jira.mycompany.ru/rest/api/2/
ã¢ãã©ã·ã¢ã³ã®Webãµã€ãã«ããJira REST APIã®è©³çŽ°ãªèª¬æ ã ãã¹ãŠã®çš®é¡ã®é¢æ°ããããããããããŒãžã§ã³ããšã«ãŸããŸãå¢ããŠããŸãããå®éã«ç¥ã£ãŠããå¿ èŠã®ããã¡ãœããã¯ããå°æ°ã§ãã
GET https://jira.mycompany.ru/rest/api/2/issue/PROJECT-1234
èŠæ±ã®åä¿¡PROJECT-1234-èŠæ±ãã£ãŒã«ããæã€JSONãè¿ãããŸãã ãã£ãŒã«ãã®ååã«ã¯ãWebã€ã³ã¿ãŒãã§ãŒã¹ã«è¡šç€ºãããååã§ã¯ãªããå éšåã䜿çšãããããšã«æ³šæããŠãã ããã ãããã£ãŠããTesting Statusããã£ãŒã«ãã¯
customfield_10234
ãŸãã ã©ã®ãã£ãŒã«ããã©ã®ãã£ãŒã«ãã«å¯Ÿå¿ããããç解ããã«ã¯ãrequest
/rest/api/2/field
ã
POST https://jira.mycompany.ru/rest/api/2/issue
æ°ãããªã¯ãšã¹ããäœæããŸãã åŒã³åºãã®æ¬æã¯ãå ¥åå¯èœãªèŠæ±ãã£ãŒã«ããæã€JSONãéä¿¡ããŸãã æž¡ãããªãã£ããã£ãŒã«ãã«ã¯ããã©ã«ãå€ãå ¥åãããŸãã
PUT https://jira.mycompany.ru/rest/api/2/issue/PROJECT-1234
ãªã¯ãšã¹ãã®ãã£ãŒã«ããå€æŽïŒç·šéïŒããŸãã JSONã¯åŒã³åºãã®æ¬æã§éä¿¡ãããŸããããã«ã¯2ã€ã®ãããã¯ããããŸãããã£ãŒã«ããå€æŽããããã®æ瀺ãæã€ãæŽæ°ããšãæ°ãããã£ãŒã«ãå€ãæã€ããã£ãŒã«ããã§ãã
å€æ°ãã£ãŒã«ãã¯ããããã®ãããã¯ã®ããããã«ã®ã¿ååšããå¿ èŠããããŸãã
äŸ
{ "update": { "summary":[ {"set":"Bug in business logic"} ], "components":[{"set":""}], "timetracking":[ {"edit":{"originalEstimate":"1w 1d","remainingEstimate":"4d"}} ], "labels":[ {"add":"triaged"}, {"remove":"blocker"}] }, "fields":{ "summary":"This is a shorthand for a set operation on the summary field", "customfield_10010":1, "customfield_10000":"This is a shorthand for a set operation on a text custom field" } }
GET https://jira.mycompany.ru/rest/api/2/search?jql=...
...-JQLèšèªã®æ¡ä»¶ã«äžèŽããã¯ãšãªã®ãªã¹ããååŸããŸã
äŸ
{ expand: "schema,names", startAt: 0, maxResults: 10, total: 738, issues: [{ expand: "operations,versionedRepresentations,editmeta,changelog,renderedFields", id: "947068", self: "https://jira.atlassian.com/rest/api/2/issue/947068", key: "JRASERVER-66937", fields: { customfield_18232: null, ...
POST https://jira.mycompany.ru/rest/api/2/search
æååã«åãŸããªãè€éãªæ¡ä»¶ã§ãåã
GET https://jira.mycompany.ru/rest/api/2/field
ã¯ãšãªã§äœ¿çšã§ãããã¹ãŠã®ãã£ãŒã«ãã®èª¬æãååŸã
GET https://jira.mycompany.ru/rest/api/2/field
ã
åããŠã§ååã§ãã
ãŸã äœãå€æŽãããç·šéãããããªãã®ã§ãã¢ãã©ã·ã¢ã³ã®JiraãµãŒããŒã JIRAãµãŒããŒïŒJIRA Coreãå«ãïŒãããžã§ã¯ã ãã€ãŸãæ¯iraçã«èšãã°Jiraã¡ã€ã³ãããžã§ã¯ãã§ãå¿åã§äœæ¥ããŸãã ããã«ãããã«ã¯ç§ãã¡ã®äººã ãããŸã
ç§ãæåã«ãå§ãããã®ã¯ããããžã§ã¯ãã®Webã€ã³ã¿ãŒãã§ãŒã¹ã«ç§»åããŠã次ã®ãããªæ¡ä»¶ã§ã¯ãšãªãæ€çŽ¢ããããšã§ãã
project = JRASERVER and updated <= -1w ORDER BY updated DESC
ããã¯ããªã¯ãšã¹ããæ£ããè¡ã£ãããšã確èªããããã«å¿ èŠã§ã-ããã§ãªãå Žåã¯ãWebã€ã³ã¿ãŒãã§ãŒã¹ãéç¥ããŸãã
æ¡ä»¶ãã³ããŒããŠãæ€çŽ¢é¢æ°ã®jqlãã©ã¡ãŒã¿ãŒã«ä»£å ¥ãããšã次ã®URLãååŸãããŸãã
https://jira.atlassian.com/rest/api/2/search?jql=project = JRASERVERããã³æŽæ°ããã<= -1w ORDER BYæŽæ°ãããDESC
ãã©ãŠã¶ã§éããJSONãåãåããŸãã ãã©ãŠã¶ã§JSONãæ¡åŒµå.jsonã®ãã¡ã€ã«ã«ä¿åããQt Creatorã§éããŸã-ãã¡ã€ã«å šäœã1è¡ã«ãªã£ãŠããããšããããããã®åŸãããªãã®æã«åŸã£ãŠãã ãã-QMLãšããŠãã©ãŒãããããŸã
GIF
å¥ã®ååã§ä¿åããŸãã çµæã®ãã¡ã€ã«ãæäœããŠããã¡ã€ã«å ã®å¿ èŠãªãã£ãŒã«ããèŠã€ããç®çã®å€ãã©ã®æ§é ã«ãããã確èªããæ¹ã䟿å©ã§ãã å ã®ãã¡ã€ã«ã¯ãã¢ãã©ã·ã¢ã³ãµãŒããŒã«å床ã¢ã¯ã»ã¹ããªãããã«ããã¹ããœãŒã¹ãšããŠåœ¹ç«ã¡ãŸãã
rest/api/2/field
ãªã¯ãšã¹ãã§ãã¹ãŠã®ãã£ãŒã«ãã®ãªã¹ããååŸããŠãå¿ èŠãªãã£ãŒã«ãããªã¹ããããŠããèå¥åãå€æããããšãæå³ããããŸãã
Qt Creatorã§ãããžã§ã¯ããäœæãã
Qt Creatorã§ãããžã§ã¯ããäœæããã«ã¯ãæšæºã®Qtã¯ã€ãã¯ã³ã³ãããŒã«ã¢ããªã±ãŒã·ã§ã³ãã³ãã¬ãŒãã䜿çšããŸãã
ããã«ãããqml.qrcãªãœãŒã¹ãã¡ã€ã«ã«main.cppãšmain.qmlã§æ§æããããããžã§ã¯ããäœæãããŸãã
main.qml
import QtQuick 2.3 import QtQuick.Controls 1.2 ApplicationWindow { id: applicationWindow1 visible: true width: 649 height: 480 title: qsTr("Hello World") menuBar: MenuBar { Menu { title: qsTr("File") MenuItem { text: qsTr("&Open") onTriggered: console.log("Open action triggered"); } MenuItem { text: qsTr("Exit") onTriggered: Qt.quit(); } } } }
ç§ãã¡ã¯ãŸã ãããã«è§ŠããŸãã;ç§ãã¡ã¯ããå·®ãè¿«ã£ãåé¡ã«å¯ŸåŠããŸãã
ãªã¯ãšã¹ãã§ã«ãŒãã®ãã¶ã€ã³ãæããŸã
æ°ããIssueCard.qmlãã¡ã€ã«ãäœæãããšããŠã£ã¶ãŒãã¯ããã©ã«ãã§ãªãœãŒã¹ãã¡ã€ã«ã«ããããããŸãã
ãªã¯ãšã¹ãã衚瀺ãããã«ãŒãã®ãã¶ã€ã³ãç§ã¯ããã«ãã¶ã€ããŒã¢ãŒãã§Qt Creatorãããã«æããŠãããæåã§QMLãå®æãããŸããã
ãšããã§ãQMLãã¶ã€ããŒã¯ãç¹ã«æåã®ããŒãžã§ã³ãšæ¯èŒããŠãæ¯èŒçåªããŠããŸãã èŠçŽ ã®ãã€ã³ãäœçœ®ã¯æ確ã«è¡šç€ºãããç°¡åã«å€æŽã§ãããããžã§ã¯ãå ã®ä»ã®qmlãã¡ã€ã«ããã³ã³ããŒãã³ããèªåçã«ãã«ã¢ããããŸãã ã»ãšãã©èœã¡ãŸããã§ãã-åŸé ãèšå®ããããšãããšãã«QtCreatorã2åããèœã¡ãŸããã§ããïŒäœãæªãããšã¯ãããŸãã-èªåä¿åãæ©èœããŸãïŒã Qt Widgetsãã¶ã€ããŒã®ãããªQMLãã¶ã€ããŒã«ã¯ããã¬ãã¥ãŒæ©èœããããŸãã
çµæã¯ãQMLãªã¯ãšã¹ãã«ãŒããIssueCard.qmlãã¡ã€ã«ã§ãã
ã³ãŒã
import QtQuick 2.0 import "methods.js" as JS Rectangle { id: rectangle1 color: "#f1dada" radius: 10 gradient: Gradient { GradientStop { position: 0.00; color: "#f5f2d8"; } GradientStop { position: 1.00; color: "#ffffff"; } } border.color: "#abfdf4" width: 300 height: 150 Text { id: keyText text: "JIRASERVER-1001" property string url: "" anchors.top: parent.top anchors.topMargin: 8 anchors.left: parent.left anchors.leftMargin: 8 font.bold: true font.pixelSize: 14 MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: Qt.openUrlExternally(parent.url) } } Text { id: summaryText y: 51 height: 42 color: "#002f7b" text: "Create a Global permission for Auditing teams to have full read only access to the instance" anchors.right: parent.right anchors.rightMargin: 8 anchors.left: parent.left anchors.leftMargin: 8 wrapMode: Text.WordWrap font.pixelSize: 15 textFormat: Text.PlainText } Image { id: priorityImage x: 276 width: 16 height: 16 anchors.top: parent.top anchors.topMargin: 9 anchors.right: parent.right anchors.rightMargin: 8 source: "minor.svg" } Image { id: typeImage x: 276 width: 16 height: 16 anchors.top: parent.top anchors.topMargin: 9 anchors.right: priorityImage.left anchors.rightMargin: 4 source: "" } Text { id: dateText x: 198 y: 31 color: "#949090" text: "13.03.2018 17:11" anchors.right: parent.right anchors.rightMargin: 8 font.pixelSize: 12 } Text { id: creatorText y: 31 color: "#949090" text: "Chung Park Chan" anchors.left: parent.left anchors.leftMargin: 8 font.pixelSize: 12 } Text { id: assigneeText x: 218 y: 128 text: "Kiran Shekhar" anchors.bottom: parent.bottom anchors.bottomMargin: 8 anchors.rightMargin: 8 anchors.right: parent.right font.pixelSize: 12 } }
ãªã¯ãšã¹ãã«å¿ããŠã«ãŒãã«èšå ¥ããã«ã¯ãæ°ããããããã£ïŒ property ïŒ issueãè¿œå ããŸãã ãã®ããããã£ã䜿çšãããšã1åã®å²ãåœãŠã§å€éšãããã¹ãŠã®ã³ã³ãã³ããå«ããªã¯ãšã¹ããã«ãŒãã«è»¢éã§ããŸãã
property var issue: null
ãããŠããã®å€æŽã®ã·ã°ãã«ã§ãå€ã解æããããããå¿ èŠãªèŠèŠã³ã³ããŒãã³ãã«æŒã蟌ãã³ãŒããèšè¿°ããŸãã
onIssueChanged: { var self = JS.getValue(issue,"self") var re = new RegExp("(https*:\/\/[^\/]+\/).+") var key = JS.getValue(issue,"key") var url = self.replace(re,'$1')+'browse/'+key keyText.text = key keyText.url = url summaryText.text = JS.getValue(issue,"fields/summary") dateText.text = (new Date(JS.getValue(issue,"fields/created"))).toLocaleString() creatorText.text = JS.getValue(issue,"fields/creator/displayName") var v = JS.getValue(issue,"fields/assignee/displayName") assigneeText.text = v === null ? "(no assigned)" : v var img = JS.getValue(issue,"fields/priority/iconUrl") var txt = JS.getValue(issue,"fields/priority/name") priorityImage.source = typeof img == 'undefined' || img === null ? "" : img img = JS.getValue(issue,"fields/issuetype/iconUrl") typeImage.source = typeof img == 'undefined' || img === null ? "" : img }
ã芧ã®ãšãããããã§ã¯JS.getValueé¢æ°ããã䜿çšããŸããé¢æ°èªäœã¯éåžžã«åçŽã§ãããè€éãªJSONæ§é ïŒååšããå ŽåïŒããã®å€ã®éžæãç°¡çŽ åããããã«äœæããŸããã
function getValue(json, path) { var arr = path.split('/'); for(var i=0; i<arr.length && json; i++) { json = json[arr[i]]; } return json; }
ãã®é¢æ°ã¯ãIssueCard.qmlã®å é ã«æ¥ç¶ãããŠããmethods.jsãã¡ã€ã«ã«ãããŸãã
ã«ãŒãã®åã«ã€ããŠèª¬æããŸã
次ã«ãåçŽã«ã¹ã¯ããŒã«å¯èœãªåã«ã«ãŒããæŽçããå¿ èŠããããŸãã å€ãã®ã«ãŒããããå Žåãã¹ã¯ããŒã«ã¯éåžžã«äŸ¿å©ã§ãã ã¹ã¯ããŒã«ããã«ã¯ãListViewãå¿ èŠã§ãã Qtã«ä»å±ããäŸã«ã¯ããQMLåçãã¥ãŒã®é åºä»ããã¥ãŒããªã¢ã«3-ãã©ãã°ãããã¢ã€ãã ã®ç§»åãã®äŸããããŸãããã®äžã§dynamicview.qmlã¯ã»ãšãã©å¿ èŠãªãã®ã§ãKanbanColumn.qmlãšããååã§ãããžã§ã¯ãã«ã³ããŒããŸãã
ããã€ãã®æ¹åãè¡ãå¿ èŠããããŸã
1ïŒåã«èŠåºããè¿œå ããæäžäœãªããžã§ã¯ãã®ããããã£ãèšå®ããŠãå€éšããåã®ååãå²ãåœãŠãŸãã
ã³ãŒã
Rectangle { id: root // property string title: "" ... // // Rectangle { id: titleRect anchors { top: parent.top left: parent.left right: parent.right margins: 2 } color: "#cfe5ff" height: titleText.height+10 Text { id: titleText text: root.title font.bold: true horizontalAlignment: Text.AlignHCenter font.pointSize: 12 anchors.centerIn: parent } } }
2ïŒãªã¯ãšã¹ãã«ãŒãã«ã¯åå¥ã®ãªããžã§ã¯ãå šäœãããã®ã§ãäŸã§äœæããåºåãColumnããã³è€æ°ã®Textãä»ããŠIssueCardã«çœ®ãæããŸãã
ã ã£ã
Rectangle { id: content ... width: dragArea.width; height: column.implicitHeight + 4 color: dragArea.held ? "lightsteelblue" : "white" Behavior on color { ColorAnimation { duration: 100 } } radius: 2 ... Column { id: column anchors { fill: parent; margins: 2 } Text { text: 'Name: ' + name } Text { text: 'Type: ' + type } Text { text: 'Age: ' + age } Text { text: 'Size: ' + size } } }
ã«ãªã£ãŠããŸã
Item { id: content ... width: dragArea.width; height: card.height + 4 ... IssueCard { id: card issue: issueRecord anchors { fill: parent; margins: 2 } } // Rectangle { anchors.fill: parent color: "lightsteelblue" visible: dragArea.held // opacity: 0.5 } }
ãã¶ã€ããŒã¯ãDelegateModelããã€ãžã§ã¹ãããªããããåã«ã€ããŠã¯ãµããŒãããŸããã äžæ¹ã§ãå®éã«ã¯å¿ èŠãããŸããããã¹ãŠæåã§è¡ãããšãã§ããŸãã
ããŒããŠã£ã³ããŠ
次ã«ãäžè¬ãŠã£ã³ããŠã§åãçµã¿ç«ãŠãå¿ èŠããããŸãã KanbanWindow.qmlãã¡ã€ã«ãäœæãããã¶ã€ããŒãå¿ èŠãªãã£ãŒã«ãããã®äžã«é 眮ããŸãã
æãåçŽãªåœ¢åŒã§ã¯ã次ã®ããã«ãªããŸãã
KanbanWindow.qml
import QtQuick 2.0 import QtQuick.Controls 1.2 Rectangle { id: rectangle1 width: 640 height: 480 color: "#e0edf6" clip: true Item { id: row1 anchors { top: parent.top left: parent.left right: parent.right margins: 4 } height: queryTE.height TextField { id: queryTE text: "file:///C:/Projects/qml/search.json" anchors.rightMargin: 4 anchors.right: goButton.left anchors.left: parent.left anchors.leftMargin: 0 } Button { id: goButton text: qsTr("Go") anchors.right: parent.right onClicked: JS.readIssues(queryTE.text) } } ListView { anchors{ top: row1.bottom bottom: parent.bottom right: parent.right left: parent.left margins: 4 } orientation: ListView.Horizontal clip: true } }
ListViewã§ã¯ã
delegate
ããããã£ã§ãã¢ãã«ã®èŠçŽ ãKanbanColumnåã®åœ¢åŒã§è¡šç€ºãããããã«æå®ããå¿ èŠããããŸããååã§ã¯ãšãªã®ãªã¹ããæž¡ãå¿ èŠãããã®ã§ã
issueList
ãšåŒã³ãŸãããã ãŸãã空ã®ã¢ãã«ãäœæããããã«
model
ãšããååãä»ã
model
ã
Rectangle { property var mainModel: [] ... ListView { ... model: ListModel { id: model } delegate: KanbanColumn { anchors.top: parent.top anchors.bottom: parent.bottom // 'groupName' title: groupName issues: issueList } } }
äžèšã§ã¯ã
mainModel
ããããã£ãäœæããŸãããããã¯ãããŒã¿ãäžæçã«ä¿åããã®ã«åœ¹ç«ã¡ãŸãã
ã¢ããªã±ãŒã·ã§ã³ãŠã£ã³ããŠã«KanbanWindowãæ¿å ¥ããããšãå¿ããªãã§ãã ããã
ApplicationWindow { id: applicationWindow1 visible: true width: 649 height: 480 title: qsTr("Hello World") ... KanbanWindow { anchors.fill: parent } }
REST APIãåŒã³åºãã³ãŒããæžã
Jiraããã¯ãšãªã®ãªã¹ããåãåããQMLã§ã¢ãã«ã«ããŒã¿ãå ¥åããã³ãŒããäœæããŸãã
QMLã¯ãå¶éãããŠããŸãããXMLHttpRequestãšJSONããŒãµãŒããµããŒãããŠããŸãïŒ ãã ã«é¢ãã詳现㪠BlackRaven86 èšäºã ãããŸã ïŒã ãããã£ãŠããµãŒããŒãžã®åŒã³åºããèšè¿°ããå¿çã解æãããã¹ãŠã®ãã®ããããŸãã
function readIssuesSimple(queryUrl) { var doc = new XMLHttpRequest(); doc.onreadystatechange = function() { if (doc.readyState == XMLHttpRequest.DONE) { var data = JSON.parse(doc.responseText); mainModel = data["issues"] model.clear() var list = mainModel // var gPath = "fields/assignee/displayName" var models = {} for(var i in list) { var item = list[i] var g = getValue(item, gPath) if(!(g in models)) models[g] = [] models[g].push({ issueRecord: item } ) } // , QML // , for(g in models) { var iss = models[g] if(g === null) g = '(null)' // 'model' - QML model.append({ groupName: g, issueList: iss }); } } } doc.open("GET", queryUrl); doc.send(); }
ãã®é¢æ°ã¯ããµãŒããŒïŒãŸãã¯ããŒã«ã«ãã¡ã€ã«ïŒãããªã¯ãšã¹ãã®ãªã¹ãããªã¯ãšã¹ãããã¬ã¹ãã³ã¹ããjsonã解æãããšã°ãŒãã¥ãŒã¿ãŒããšã«ãªã¯ãšã¹ããã°ã«ãŒãåããQMLã§ã¢ãã«ãæºãããŸãã
é¢æ°ããã¿ã³ã«æ¥ç¶ããŸã
Button { id: goButton text: qsTr("Go") anchors.right: parent.right onClicked: JS.readIssuesSimple(queryTE.text) }
ãããŠãäœæ¥ã確èªããŸãã
GIF
åºæ¬çã«ãããŒãã¯æºåãã§ããŠããŸãã ããã«ããã®æ¹åãšéçºã«åãçµãããšãã§ããŸãã
ç§ã¯ã»ãšãã©å¿ããŠããŸãã-ããšãã°ãå®éã®JiraãµãŒããŒãžã®URLãæå®ããããšãããšã
https://jira.atlassian.com/rest/api/2/search?maxResults=50&jql=project = JRASERVERããã³æŽæ°ããã<= -1wã§ãæ åœè ã空ã§ã¯ãªãORDER BYãæŽæ°ãããASC
Windowsã䜿çšããŠããå Žåãã»ãšãã©ã®å ŽåæåããŸããã SSLã®åé¡ã¯ããããã¬ãŒã§ããã°ã©ã ãèµ·åãããšãã«ãQt Creatorãç°å¢å ã®OpenSSLã©ã€ãã©ãªãžã®ãã¹ãç»é²ããªãããšã§ãã libeay32.dllãšssleay32.dllãäœæãããå®è¡å¯èœãã¡ã€ã«ã«ã³ããŒããŠã楜ãã¿ãã ããã
ãã©ã¡ãŒã¿ãŒãä¿åããã³åŸ©å ããããã®LocalStorage
æ¯åJiraãµãŒããŒã«URLãå ¥åããªãããã«ããã«ã¯ãå ¥åããæååãä¿åããèµ·åæã«åŸ©å ãã䟡å€ããããŸãã ã¯ããQMLã¯LocalStorageã§å®è¡ã§ããŸãã
ãã©ã¡ãŒã¿ãŒã®èªã¿åããšä¿åã®ããã®é¢æ°ãäœæããŸãã
function loadSettings() { var dbConn = LocalStorage.openDatabaseSync("JKanban", "1.0", "", 1000000); dbConn.transaction( function(tx) { // Create the database if it doesn't already exist tx.executeSql('CREATE TABLE IF NOT EXISTS Settings(skey TEXT, svalue TEXT)'); var rs = tx.executeSql('select skey, svalue from Settings') var r = "" var c = rs.rows.length for(var i = 0; i < rs.rows.length; i++) { var skey = rs.rows.item(i).skey var svalue = rs.rows.item(i).svalue if(skey === 'query') queryTE.text = svalue } } ) } function saveSetting(skey, svalue) { var dbConn = LocalStorage.openDatabaseSync("JKanban", "1.0", "", 1000000); dbConn.transaction( function(tx) { tx.executeSql('delete from Settings where skey = ?', [ skey ]); tx.executeSql('INSERT INTO Settings VALUES(?, ?)', [ skey, svalue ]); } ) }
ãã©ã¡ãŒã¿ãä¿åããåŒã³åºããè¿œå ããŠ...
function readIssuesSimple(queryUrl) { saveSetting('query',queryUrl)
... KanbanWindowãäœæãããšãã®åŸ©å
Rectangle { id: rectangle1 width: 640 height: 480 color: "#e0edf6" clip: true Component.onCompleted: JS.loadSettings() ....
ã°ã«ãŒãåãªãã·ã§ã³ãè¿œå ãã
ã¢ãŒãã£ã¹ãããšã«ã°ã«ãŒãåãããšãã¹ããŒã¿ã¹ãåªå 床ãªã©ãä»ã®ã°ã«ãŒãåãªãã·ã§ã³ãéžæã§ããããã«ãªãã®ã¯çã«ããªã£ãŠããŸãã ãããKanbanParams.qmlã°ã«ãŒãåãªãã·ã§ã³ããã«ã®è¡šç€ºæ¹æ³ã§ãã
KanbanParams.qml
import QtQuick 2.0 import QtQuick.Controls 1.2 import QtQuick.LocalStorage 2.0 import "methods.js" as JS Item { width: 480 height: cbGroupField.height property alias groupVariant: cbGroupField.currentIndex property string groupValuePath: cbGroupField.model.get(cbGroupField.currentIndex).namePath property alias groupList: groupsTE.text Text { id: label height: cbGroupField.height text: qsTr(":") verticalAlignment: Text.AlignVCenter } ComboBox { id: cbGroupField anchors { left: label.right; leftMargin: 4 } model: ListModel { ListElement { text: qsTr(" ") namePath: "fields/status/name" } ListElement { text: qsTr(" ") namePath: "fields/assignee/displayName" } ListElement { text: qsTr(" ") namePath: "fields/creator/displayName" } ListElement { text: qsTr(" ") namePath: "fields/issuetype/name" } ListElement { text: qsTr(" ") namePath: "fields/priority/name" } } } TextField { id: groupsTE text: '' anchors { right: buttonGroups.left rightMargin: 4 left: cbGroupField.right leftMargin: 4 } } Button { id: buttonGroups text: qsTr("") anchors.right: parent.right onClicked: JS.repaintKanban() } }
ã芧ã®ãšãããããã§ã¯ComboBoxã«ã¯ã°ã«ãŒãåãªãã·ã§ã³ãå¯èœãªã¢ãã«ãå«ãŸããŠãããåèŠçŽ ã«ã¯ã°ã«ãŒãã決å®ããããã«äœ¿çšãããå€ãžã®JSONãã¹ããããŸãã ãããã£ãŠãã°ã«ãŒãåãªãã·ã§ã³ã®æ°ã¯èªç±ã«æ¡åŒµã§ããŸãã
æäžäœã§ã¯ãããããã£ãå®çŸ©ãããŸãããã®ãã¡ã®2ã€ã¯å éšå€ã®ãšã€ãªã¢ã¹ã§ãã LocalStorageããèªã¿åã£ãŠç®çã®å€ãå²ãåœãŠãã«ã¯ããšã€ãªã¢ã¹ãå¿ èŠã§ãã groupValuePathããããã£ã«é¢ããŠïŒ
property string groupValuePath: cbGroupField.model.get(cbGroupField.currentIndex).namePath
次ã«ãåã«çŸåšã®ã°ã«ãŒãåãªãã·ã§ã³ã®å€ãžã®ãã¹ãè¿ããŸãã
KanbanParamsãKanbanWindowã«æ¿å ¥ãããšã次ã®ãŠã£ã³ããŠã衚瀺ãããŸãã
次ã¯ïŒ
çµæã®ããŒãã¯ããªã¯ãšã¹ãã䜿çšããŠçŸåšã®ç¶æ³ã衚瀺ããããã«æ¢ã«äœ¿çšã§ããŸãããæ¹åããããšãã§ããŸãã
- åã«äžŠã¹æ¿ãã«ãŒããäœæããŸãã ããšãã°ããªã¯ãšã¹ãã®åªå
床ã«ãã£ãŠã ãããŠã
ãºãã³ã®è²ã®éããç³ãèš³ãããŸããããåªå 床ãšãªã¯ãšã¹ãã®çš®é¡ã«ãããªã¯ãšã¹ãã ç§ãè©Šãã-éåžžã«å¿«é©ããå§ãããŸãã - æ°ããåã«å¯Ÿå¿ããå€ãæã€åéã§ã«ãŒãããã©ãã°ã¢ã³ãããããããŸãã ãšããã§ãJiraã§ã¯ã¹ããŒã¿ã¹ã¯å²ãåœãŠã§ã¯ãªãé·ç§»ã«ãã£ãŠå€æŽãããããããã®æ¹æ³ã§ã¹ããŒã¿ã¹ãå€æŽããããšã¯ã§ããŸããã
- ããŒãå ã§æ°ããã¯ãšãªãå ¥åããŸãã
- åã®2ã€ã®ãã€ã³ãã«ã¯æ¿èªãå¿ èŠã§ãã ãããªããã
- QML以å€ã«äœããªãã®ã§ããããžã§ã¯ãã¯AndroidãšiOSçšã«çµã¿ç«ãŠãããšãã§ããŸã-å€æŽãªãã§åäœããã¯ãã§ãã
ã³ãŒãã¯GitHubã«æçš¿ãããŠããŸã ã