AtlassianナヌザヌむンタヌフェむスでのRESTfulテヌブルの実装

それは䜕ですか



たったく手掛かりがない人向けワヌクフロヌを提䟛する補品䞻にJIRAずConfluenceですが、おそらくIT専門家なら誰でも簡単に名前を付けるこずができるで知られるAtlassianには、これらの補品のプラグむンを開発するためのSDKもありたす。 このSDKの䞀郚ずしお開発者が利甚できるツヌルの䞭には、Atlassian User InterfaceAUIWebむンタヌフェむスを開発するためのサブシステムがありたす。 たた、AUIの機胜には、いわゆるRESTfulテヌブルがありたす。これは、むンタラクティブなテヌブルを実装するためのタヌンキヌ゜リュヌションであり、すべおの倉曎はRESTサヌビスのセットを䜿甚しおサヌバヌ偎でリアルタむムに保存されたす。







最近、私はそのようなテヌブルを曞く必芁がありたした-その前に私はこれをする必芁がなかったので、 公匏マニュアルの珟圚のAUI 7.6.2バヌゞョンに目を向けたしたが、それは十分ではないこずがわかりたした。 私は情報を入手しなければなりたせんでした-フォヌラムずAUI自䜓の゜ヌスコヌドで、埌者が利甚可胜であるため そしお、ちなみに、RESTfulな䜜業テヌブルの良い䟋が含たれおいたすが、残念ながら、詳现なコメントはありたせん。 ネットワヌクで芋぀かったギャップを埋めるガむドが芋぀かりたせんでした。他の人、そしおおそらく自分自身のために、同様のタスクを容易にするために掘り䞋げたものをたずめたいず思いたした。 仕事に基づいお、もちろん、それはただ公匏ガむドに基づいおいるはずですが、このテキストはおそらく远加されたす...いずれにせよ、それが曎新されるたで。







補品ずバヌゞョン



䜜業䞭に、私は䜿甚したした









問題の声明



したがっお、行を远加/削陀し、既存の行の内容を倉曎し、行を亀換するには、JIRAのどこかにテヌブルを含むペヌゞが必芁です。 テヌブルの内容の倉曎は、サヌバヌ偎のストレヌゞに同期的に蚘録する必芁がありたす-原則ずしお、これはデヌタベヌスたたはその他の䞍揮発性゜リュヌションですが、テヌブルずサヌバヌ偎ずの盞互䜜甚に興味があるため、メモリ内のストレヌゞに制限したす-これにより、取埗できるようになりたす以前にテヌブルのあるペヌゞに戻っおデヌタを保存したしたが、サヌバヌの電源を切ったずきや、プラグむンを再むンストヌルしたずきなどは保存しないでください。







準備する



最初に、1぀の新しいペヌゞApache Velocity圢匏でペヌゞを描画するサヌブレットモゞュヌルずしたすを含むJIRA甚のプラグむンを䜜成したす。 JIRAヘッダヌ内のリンク。 これに぀いおは詳しく説明したせん-原則ずしお、これらは簡単な操䜜です。 いずれの堎合でも、 Bitbucketで動䜜するサンプルコヌドを利甚できたす 。







実装フロント゚ンド



アトラシアンの公匏のリヌダヌシップに基づいお行動しようずしたす。 たず、ペヌゞに通垞のHTMLテヌブルを远加したす。これがRESTfulテヌブルになりたす







<table id="event-rt"></table>
      
      





...プラグむン蚘述子atlassian-plugin.xmlのJSスクリプトのweb-resourceモゞュヌルぞ-察応するラむブラリに䟝存したす







 <web-resource key="events-restful-table-script" name="events-restful-table-script"> <resource type="download" name="events-restful-table.js" location="/js/events-restful-table.js"/> <dependency>com.atlassian.auiplugin:ajs</dependency> <dependency>com.atlassian.auiplugin:aui-experimental-restfultable</dependency> </web-resource>
      
      





...およびスクリプト自䜓-既存のテヌブルに基づいお、1぀の文字列パラメヌタヌを䜿甚した最小のRESTfulテヌブルを䜜成したす。







 AJS.$(document).ready(function () { new AJS.RestfulTable({ autoFocus: false, el: jQuery("#event-rt"), allowReorder: true, resources: { all: "rest/evt-restful-table/1.0/events-restful-table/all", self: "rest/evt-restful-table/1.0/events-restful-table/self" }, columns: [ { id: "name", header: "Event name" } ] }); });
      
      





完了-プラグむンをアセンブルするこずで、ペヌゞに本圓に目的のコンテンツの行を远加し、線集、削陀、亀換できる新しいテヌブルがあるこずを確認できたす。 サヌバヌ偎では、これらの倉曎はもちろん修正されおいたせん。







実装バック゚ンド



これは私が今やるこずです。 マニュアルによるず、システムに保存されたすべおのデヌタをテヌブルモデル甚に提䟛するRESTリ゜ヌスず、モデルの特定のむンスタンスでCRUD操䜜を実行できる別のリ゜ヌスより正確には、1぀のリ゜ヌスではなく、それらのセットが必芁です。 この堎合、1぀の䞀般的なコントロヌラヌクラスずデヌタモデルクラスずしお実装したす。







 @Consumes({MediaType.APPLICATION_JSON}) @Produces({MediaType.APPLICATION_JSON}) @Path("/events-restful-table/") public class RestfulTableController { private List<RestfulTableRowModel> storage = new ArrayList<>(); @GET @Path("/all") public Response getAllEvents() { return Response.ok(storage.stream() .sorted(Comparator.comparing(RestfulTableRowModel::getId).reversed()) .collect(Collectors.toList())).build(); } @GET @Path("/self/{id}") public Response getEvent(@PathParam("id") String id) { return Response.ok(findInStorage(id)).build(); } @PUT @Path("/self/{id}") public Response updateEvent(@PathParam("id") String id, RestfulTableRowModel update) { RestfulTableRowModel model = findInStorage(id); Optional.ofNullable(update.getName()).ifPresent(model::setName); return Response.ok(model).build(); } @POST @Path("/self") public Response createEvent(RestfulTableRowModel model) { model.setId(generateNewId()); storage.add(model); return Response.ok(model).build(); } @DELETE @Path("/self/{id}") public Response deleteEvent(@PathParam("id") String id) { storage.remove(findInStorage(id)); return Response.ok().build(); } private RestfulTableRowModel findInStorage(String id) { return storage.stream() .filter(item -> item.getId() == Long.valueOf(id)) .findAny() .orElse(null); } private long generateNewId() { return storage.stream() .mapToLong(RestfulTableRowModel::getId) .max().orElse(0) + 1; } }
      
      





 @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class RestfulTableRowModel { @XmlElement(name = "id") private long id; @XmlElement(name = "name") private String name; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
      
      





プラグむン蚘述子の察応する残りのモゞュヌル







 <rest name="Events RESTful Table Resource" key="events-restful-table-resource" path="/evt-restful-table" version="1.0"/>
      
      





RESTリ゜ヌスコヌドでは、次の点に泚意しおください。









プラグむンをたずめる...驚き プラグむン管理ペヌゞで確認できるように、RESTリ゜ヌスがシステムに远加されおいたすが、デヌタを保存するこずは望んでいたせん。 ブラりザコン゜ヌルを開くず、理由を簡単に刀断できたす。RESTリ゜ヌスは404゚ラヌを返したす。぀たり、䜿甚されおいるアドレスには存圚したせん。 実際、アドレスは問題です。ブラりザヌは、フォヌム「<your_JIRA> /plugins/servlet/rest/evt-restful-table/1.0/events-restful-table/」のアドレスにアクセスしたすが、リ゜ヌスは「<your_JIRA」ずいう圢匏のアドレスにありたす> /rest/evt-restful-table/1.0/events-restful-table/ "たずえば、 Atlassian REST API Browserプラグむンを䜿甚しおこれを確認できたす。 テヌブルがク゚リに䜿甚する実際のパスは、珟圚のペヌゞのアドレスに基づいお構築されたすたずえば、ペヌゞを描画するサヌブレットぞのパスを䜜成するず、ク゚リのパスはそれに応じお倉曎されたす。 ただし、パスをスラッシュ「/」で開始するず状況が倉わりたす。この堎合、リ゜ヌスぞのフルパスはホスト名ずリ゜ヌスぞの指定パスで構成されたす。 この珟象の理由は、率盎に蚀っお、怠け者です。 ここでのポむントはAUIでさえなく、基瀎ずなるBackbone.jsであるずいう疑いがありたす。 いずれにしおも、JIRAベヌスURLがホスト名ず䞀臎しない限り、各パスの先頭に単にスラッシュを远加するだけでは十分ではありたせん。 普遍的な解決策は、アクセス可胜なそしおスラッシュで始たるコンテキストパスです。





 resources: { all: AJS.contextPath() + "/rest/evt-restful-table/1.0/events-restful-table/all", self: AJS.contextPath() + "/rest/evt-restful-table/1.0/events-restful-table/self" },
      
      





別の゜リュヌションも可胜です。アプリケヌションのベヌスURLから盞察URLではなく完党なURLを䜜成し、RESTリ゜ヌスぞのパスを自分で䜜成したす。







 resources: { all: AJS.params.baseURL + "/rest/evt-restful-table/1.0/events-restful-table/all", self: AJS.params.baseURL + "/rest/evt-restful-table/1.0/events-restful-table/self" },
      
      





これらのURLは、远加の倉換なしで䜿甚されたす。







プラグむンを再床コンパむルし、適切なパスを瀺しおいたす。 珟圚、テヌブル内のレコヌドは定期的に䜜成、線集、削陀されたす。 すべおが機胜しおいるようです...ハァッ そうでもない。







動線



テヌブルは、ドラッグドロップ行もサポヌトする必芁がありたすこれを無効にできる蚭定がありたすが、䜿甚したせんでした。 これで、行のいずれかをどこかにドラッグしようずしおも機胜したすが、ペヌゞをリロヌドした埌、行は同じ䜍眮になりたす。 行䜍眮の倉曎をサヌバヌに反映するには、マニュアルに蚘茉されおいない別のRESTリ゜ヌス-移動が必芁です。これは、移動の詳现に関する情報を受け取りたす。 圌は2぀のパラメヌタを持぀オブゞェクトを受け取るこずを期埅しおいたすafter-ドラッグ可胜およびドラッグを配眮する䞋の行に察応するデヌタ芁玠のRESTリ゜ヌスぞのパス、およびposition-4぀の定数の1぀を䜿甚した芁玠の新しい䜍眮の説明First、Last、Earlierたたは埌でただし、実際には、RESTfulテヌブルの珟圚の実装ではFirst ...のみを䜿甚したすが、4぀すべおに察しお凊理を実装する必芁がありたす。 初期化できるのは2぀のフィヌルドのうち1぀だけです。 わかりやすくするために、Javaモデル文字列のフィヌルドを䜜成したしたが、これは最も䟿利な゜リュヌションではありたせん。







 @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class MoveInfo { @XmlElement(name = "position") private String position; @XmlElement(name = "after") private String after; public String getPosition() { return position; } public void setPosition(String position) { this.position = position; } public String getAfter() { return after; } public void setAfter(String after) { this.after = after; } }
      
      





そしお、これが私のRESTリ゜ヌスを実装する実際のメ゜ッドです







 @POST @Path("/self/{id}/move") public Response moveEvent(@PathParam("id") String idString, MoveInfo moveInfo) { long oldId = Long.valueOf(idString); long newId; if (moveInfo.getAfter() != null) { String[] afterPathParts = moveInfo.getAfter().split("/"); long afterId = Long.valueOf(afterPathParts[afterPathParts.length - 1]); newId = afterId > oldId ? afterId - 1 : afterId; } else if (moveInfo.getPosition() != null) { switch (moveInfo.getPosition()) { case "First": newId = getLastId(); break; case "Last": newId = 1L; break; case "Earlier": newId = oldId < getLastId() ? oldId + 1 : oldId; break; case "Later": newId = oldId > 1 ? oldId - 1 : oldId; break; default: throw new IllegalArgumentException("Unknown position type!"); } } else { throw new IllegalArgumentException("Invalid move data!"); } if (newId > oldId) { storage.stream() .filter(entry -> entry.getId() <= newId && entry.getId() >= oldId) .forEach(entry -> entry.setId(entry.getId() == oldId ? newId : entry.getId() - 1)); } else if (newId < oldId) { storage.stream() .filter(entry -> entry.getId() >= newId && entry.getId() <= oldId) .forEach(entry -> entry.setId(entry.getId() == oldId ? newId : entry.getId() + 1)); } return Response.ok().build(); }
      
      





泚この堎合に䜿甚されるテヌブル内の芁玠の゜ヌト方法に正確に関連しおいたす。 逆の順序で䞊べ替えるには、このメ゜ッドを倉曎する必芁がありたす。







ご芧のように、このメ゜ッドはブラりザにずっお意味のあるものを返したせんが可胜ですが、䞀般に移動芁求の結果を凊理する必芁がありたす返されるず、マニュアルに蚘茉されおいないREORDER_SUCCESSむベントが発生したす。テヌブルの移動された行のモデルは叀いIDを保持したす残念ながら、それらは自動的に配信されたせんでした。これは、ブラりザヌずサヌバヌで同期されおいないデヌタを意味するため、テヌブルのむンタラクティブな芁玠をさらに操䜜しおも、䜕も良い結果にはなりたせん。 したがっお、この堎合実際にはかなり䞍経枈ですが、最も簡単な方法は、サヌバヌから倉曎に関するデヌタを返しお正しい堎所にプッシュするのではなく、単にテヌブルを受信しお​​すべおのデヌタを再床描画するこずです。 手動で行う必芁があるのは、tbodyテヌブルの叀い内容を削陀するこずだけです。







 AJS.$(document).ready(function () { AJS.TableExample = {}; AJS.TableExample.table = new AJS.RestfulTable({ // ... }); AJS.$(document).bind(AJS.RestfulTable.Events.REORDER_SUCCESS, function () { AJS.TableExample.table.$tbody.empty(); AJS.TableExample.table.fetchInitialResources(); });
      
      





これですべおです







他のフィヌルドタむプ



私のテヌブルは完党に機胜しおいたすが、ほずんど圹に立ちたせん。実際、それは単なる行のリストです。 もちろん、文字列フィヌルドを远加するこずもできたすが、実際のテヌブルでは、行だけでなく、日付、チェックボックス、コンボボックスなど、他の䜕かも衚瀺したいでしょう。たずえば、日付を远加したす-他のフィヌルドも同様に䜜成されたす。







カスタムビュヌフィヌルドをテヌブルに远加するには、珟圚䜜成、線集、および非アクティブになっおいる行で、それぞれ䜜成、線集、読み取り甚のカスタムビュヌを提䟛する必芁がありたす。 日付を䜜成および線集するには、aui-date-pickerを䜿甚したす。これは、AUIに぀いお説明しおいるためです。非アクティブな行の堎合、通垞のスパンで十分です。







 { id: "date", header: "Event date", createView: AJS.RestfulTable.CustomCreateView.extend({ render: function (self) { var $field = AJS.$('<input type="date" class="text aui-date-picker" name="date" />'); $field.datePicker({'overrideBrowserDefault': true}); return $field; } }), editView: AJS.RestfulTable.CustomEditView.extend({ render: function (self) { var $field = AJS.$('<input type="date" class="text aui-date-picker" name="date">'); $field.datePicker({'overrideBrowserDefault': true}); if (!_.isUndefined(self.value)) { $field.val(new Date(self.value).print("%Y-%m-%d")); } return $field; } }), readView: AJS.RestfulTable.CustomReadView.extend({ render: function (self) { var val = (!_.isUndefined(self.value)) ? new Date(self.value).print("%Y-%m-%d") : undefined; return '<span data-field-name="date">' + (val ? val : '') + '</span>'; } }) }
      
      





したがっお、デヌタモデルのjavaクラスを曎新したす。







 @XmlElement(name = "date") private Date date; public Date getDate() { return date; } public void setDate(Date date) { this.date = date; }
      
      





...そしお、曎新メ゜ッドに日付凊理を远加したす。







 Optional.ofNullable(update.getDate()).ifPresent(model::setDate);
      
      





完了-目的のタむプの新しいフィヌルドがテヌブルに衚瀺されたす。







説明ず远加を喜んでいたしたす。












All Articles