2023年9月29日金曜日

パッケージDBMS_CLOUD_PIPELINEを使用してビューAPEX_WORKSPACE_ACTIVITY_LOGの内容をオフロードする

Oracle APEXでは、詳細なアクティビティのログをビューAPEX_WORKSPACE_ACTIVITY_LOGから参照できるようになっています。このログの保存期間は、APEXの管理サービスのインスタンスの設定ログ間隔の管理から変更できます。


デフォルトでは、APEX_WORKSPACE_ACTIVITY_LOGは14日間ごとにログの切り替えが発生します。


これらのログを長期間保管したいという要件はあるかと思います。単純にログ切替えまでの経過日数を増やすとパフォーマンスに悪い影響がでます。また、APEXがインストールされているスキーマの表領域に(大抵はSYSAUXを使っていると思います)多くの容量が必要になります。

Autonomous Databaseでは、パッケージDBMS_CLOUD_PIPELINEが提供されています。このパッケージを使用し、ビューAPEX_WORKSPACE_ACTIVITY_LOGの内容をオブジェクト・ストレージにエクスポートできます。

APEXのワークスペース・スキーマにDBMS_CLOUD_PIPELINEの実行権限を与えます。

grant execute on dbms_cloud_pipeline to <APEXワークスペース・スキーマ>;


ビューAPEX_WORKSPACE_ACTIVITY_LOGをAPEXのワークスペース・スキーマから検索すると、検索される対象のログは、そのワークスペースに限定されます。内部ワークスペース(管理ツールや開発ツールのログを含む)を含んだすべてのワークスペースのアクティビティ・ログをエクスポートする場合は、スキーマADMINでDBMS_CLOUD_PIPELINEのパイプラインを作成し、実行します。

今回は作業をAPEXのSQLコマンドで実施したいので、ワークスペースに限定してログのオフロードを行います。

最初にログのエクスポート先となるバケットを、オブジェクト・ストレージに作成します。

Oracle Cloudのコンソールより、ストレージバケットを開きます。


バケットの作成を実行します。今回はAPEX_WORKSPACE_ACTITIVY_LOGというバケットを作成しました。


クリデンシャルの作成方法やリソース・プリンシパルの設定方法は他の説明を参照していただき、今回は手っ取り早く事前認証済リクエストを使います。

右端のアイコンよりメニューを開き、事前承認済リクエストの作成を実行します。


事前承認済リクエスト・ターゲットとしてバケットを選択します。アクセス・タイプとしてオブジェクトの書込みを許可(事前承認済ターゲットからオブジェクトを読むことはありません)を選択します。有効期限も確認します。

事前承認済リクエストの作成をクリックします。


事前承認済リクエストが作成されます。現在のURLは非推奨とのことなので、推奨されるURLをコピーしておきます。


APEXのSQLコマンドよりパイプラインAPEX_ACTIVITY_LOG_EXPORTを作成します。DBMS_CLOUD_PIPELINE.CREATE_PIPELINEを呼び出します。

以下のコードを実行します。

事前承認済リクエストの部分は、作成したURLで置き換えます。ログはそのバケットの下にプレフィックスactivityが付与されたファイルとして作成されます。

テストなのでintervalとして2分を指定しています。table_nameAPEX_WORKSPACE_ACTIVITY_LOGkey_columnVIEW_TIMESTAMPです。その他のattributeの詳細は、パッケージDBMS_CLOUD_PIPELINEの説明を参照してください。




作成したパイプラインを確認します。ビューUSER_CLOUD_PIPELINESを検索します。

select * from user_cloud_pipelines;


パイプラインをスタートします。DBMS_CLOUD_PIPELINE.START_PIPELINEを呼び出します。

start_dateとして指定した日時よりintervalで指定した時間が経過した時刻が、初回実行の時刻となります。


パイプラインの実行履歴は、ビューUSER_CLOUD_PIPELINE_HISOTRYより確認できます。

select * from user_cloud_pipeline_history;


バケットの一覧を見ると、ファイルが作成されていることが確認できます。

初回実行時は列VIEW_TIMESTAMPによる制限がかかっていないため、formatのmaxfilesizeのデフォルトである10MiBのファイルが作成されています。


以上でビューAPEX_WORKSPACE_ACTIVITY_LOGのエクスポートが確認できました。

パイプラインの停止はDBMS_CLOUD_PIPELINE.STOP_PIPELINE、削除はDBMS_CLOUD_PIPELINE.DROP_PIPELINEを呼び出します。

パッケージDBMS_CLOUD_PIPELINEの紹介は以上になります。

2023年9月27日水曜日

サンプル・アプリケーションSample Graph Visualizationsの紹介

 Oracle APEXのサンプル・アプリケーションとしてSample Graph Visualizationsが追加されています。インストールできるのは、APEX 23.1以降です。

https://oracle.github.io/apex/


最近のAPEX Office HourにてOracle社のSpatial and Graph Product Management TeamのRahul Taskerさんが、APEX and Property Graphs in Oracle Database 23cという題でオンライン・セミナーをされています。録画はこちら

Property GraphはOracle Database 23cから提供される機能ですし、その話だから使えるのはまだ先かな、と思って聞いていたところ、Jayant Sharmaさんが「Sample Graph VisualizationsはADB Serverlessでも使えるよ。」とのこと。

ということで、サンプルをダウンロードとしてAlways FreeのADB Serverlessにインストールしてみました。問題なく動きました。

アプリケーションを実行するとログイン画面が表示されます。


サインインすると、沢山のサンプルが表示されます。


Basic Graphを開いてみると、以下のように表示されます。


Graph Studioで使われているビジュアリゼーションのJavaScriptコンポーネントを、APEXのプラグインとして移植しているように見えます。


Sample Graph Visualizationsでは、グラフを表示するリージョンのタイプGraph Visualizations (Preview)となっています。

将来は標準のリージョンになるかもしれません。


このグラフのソースとなっているQueryを確認した範囲では、ソースはverticesとedgesの属性を、それぞれ配列として含むJSONドキュメントであれば良いみたいです。つまり、必ずしもOracle Database 23cのProperty Graphの機能が無くても、このプラグインは使えます。


Stylingを開くと以下のようなグラフが確認できます。


Interactionでは、APEXの動的アクションを使った実装がされています。


Selectionでは、さらに凝った実装が行われています。


Network Evolutionでは、チャートを重ねて表示しています。


その他にSaving Graph State、Keyboard Navigation Shortcutsといったページがあります。

Oracle Database 23cにProperty Graphが実装されても、APEXで使用するにはビジュアリゼーションがネックだと思っていたので、このようなコンポーネントが使えるようになっているのは、とてもありがたいです。

ちなみにAPEX 23.1とOracle Database 23cを組み合わせた場合は、ソースタイプとしてプロパティ・グラフを指定できるようになります。


SELECT文全体を記述する必要がなくなり、若干ソースの設定が容易になります。

以上です。

Oracle APEXのアプリケーション作成の参考になれば幸いです。

動的アクションから開いたモーダル・ダイアログのページのクローズ・イベントを取る方法

動的アクションよりモーダル・ダイアログのページ(以下フォームと記します)を開いた場合、イベントのダイアログのクローズが取れない(TRUEアクションが実行されない)との相談がありました。

原因と対処方法について紹介します。

最初に検証に使用する簡単なアプリケーションを作成します。

アプリケーション作成ウィザードを起動します。アプリケーションの名前Dialog Close Eventとします。

ホーム・ページは削除し、サンプル・データセットのEMP/DEPTに含まれる表EMPをソースとした、対話モード・レポートとフォームのページを追加します。


対話モード・レポートのページ名EMPとし、表またはビューとしてEMPを選択します。フォームを含めるチェックをいれ、ページの追加を行います。


以上で、アプリケーションを作成します。

ページ・デザイナで、ページ番号の表EMPの対話モード・レポートのページを開きます。


動的アクションでフォームを開く実装を追加します。

フォームを開くURLを保持するページ・アイテムとしてP1_URLを作成します。

一般的な実装ではタイプとして非表示を選択しますが、今回は検証なのでテキスト・フィールドを選択します。


フォームを開く際の引数となる従業員番号を保持するページ・アイテムとしてP1_EMPNOを作成します。タイプテキスト・フィールドです。


フォームを開くボタンとしてOPENを作成します。動作アクションとして動的アクションで定義を選択します。


ボタンOPENに動的アクションを作成します。

識別名前onClick OPENとします。タイミングイベントはデフォルトのクリックです。


最初のTRUEアクションで、フォームを開くためのURLを生成します。

アクションとしてサーバー側のコードを実行を選択し、設定PL/SQLコードとして以下を記述します。
:P1_URL := apex_page.get_url(p_page => 2, p_items => 'P2_EMPNO', p_values => :P1_EMPNO);
追記: 以下のようにapex_page.get_urlの引数p_triggering_elementとして対話モード・レポートの静的IDを指定すると、JavaScriptによるワークアラウンドは不要でした。
:P1_URL := apex_page.get_url(p_page => 2, p_items => 'P2_EMPNO', p_values => :P1_EMPNO, p_triggering_element => 'emp');

送信するアイテムとしてP1_EMPNOを指定し、戻すアイテムとしてP1_URLを指定します。

ボタンのアクションとしてこのアプリケーションのページにリダイレクトを選択してフォームを開く場合は、ページが表示された時点でのページ・アイテムの値が引数となります。ページ表示後に変更したページ・アイテムの値を引数とすることはできないため、このような場合は、動的アクションとして実装します。

実行結果を待機はかならずオンにします。初期化時に実行する必要は無いので、これはオフにします。


ページ・アイテムP1_URLとして設定されたページを開くTRUEアクションを作成します。

アクションとしてJavaScriptコードの実行を選択し、設定コードとして以下を記述します。
apex.navigation.redirect(apex.items.P1_URL.value);

以上でアプリケーションを実行します。

Empnoとして7788を入力し、ボタンOPENをクリックします。


指定した従業員番号でフォームが開きます。動的アクションは適切に実装されていることがわかります。

Commissionなどを変更し、変更の適用をクリックします。


変更の適用をクリックするとフォームが閉じますが、対話モード・レポートがリフレッシュされないため、変更した値がレポートに反映されません。


このようになる理由は、ダイアログのクローズに登録されている動的アクションとファンクションapex.navigation.dialog(apex.theme42.dialog)呼び出し時のイベントのターゲットの不一致にあります。

ページ作成ウィザードで対話モード・レポートとフォームのページを作成すると、レポートの編集 - ダイアログのクローズという名前で、レポートをリフレッシュする動的アクションが作成されます。

この動的アクションのタイミング選択タイプとしてリージョンリージョンとして対話モード・レポートのリージョンであるEmployeesが選択されています。


ページ・アイテムP1_URLに実際にフォームを開くURLが設定されています。こちらを確認すると、以下のようになっています。
javascript:apex.theme42.dialog('\u002Fords\u002Fr\u002Fapexdev\u002Fdialog-close-event\u002Femployee?p2_empno=7788\u0026session=107992989464019\u0026cs=3mbMpJtQdUFyCb1YAXSH6hLtMuzCcsW93-WP8d5oMfp9J5S4Dw5nDFMEHLvOPgfUVORO3IxljtqC_NTpKInotEw\u0026dialogCs=kwCn_71eCWwNlEi-AzXtGDzAVWhhenYNbvXNDRUKCKLRDluOCNLUMotdGi_ZlKcPv5qzmDil0lWfd4yV41Z1bA',{title:'Employee',w:'720',mxw:'960',modal:true,dialog:null,dlgCls:'t-Drawer-page--standard '+''},' js-dialog-class-t-Drawer--pullOutEnd',this)
JavaScript APIのapex.theme42.dialogを呼び出していますが、APIリファレンスのapex.navigation.dialogを同等なので、以下のリファレンスを参照します。

https://docs.oracle.com/en/database/oracle/apex/23.1/aexjs/apex.navigation.html#.fn:dialog

第4引数のpTriggeringElementに指定されたHTML要素にapexafterclosedialog、apexafterclosecanceldialogのイベントが発行されると記載されています。つまり、動的アクションのタイミングとして設定されている要素がpTriggeringElementとして指定されていないと、ダイアログのクローズとして作成されている動的アクションは動作しません。

以上より、ページ・アイテムP1_URLとして生成されたJavaScriptを実行する際のthisを、対話モード・レポートのリージョンEmployeesとなるようにします。

色々な方法があるとは思いますが、今回はcallを使ってみます。

pTriggeringElementとして指定するために、対話モード・レポートのリージョンに静的IDとしてempを設定します。


ページ・プロパティJavaScriptファンクションおよびグローバル変数の宣言として、以下を記述します。
function openDialog(url) {
    eval(url);
}

TRUEアクションJavaScriptコードの実行コードを以下に置き換えます。
openDialog.call(apex.jQuery("#emp"),apex.items.P1_URL.value);


以上で対応は完了です。

フォームをクローズしたときにレポートがリフレッシュされるようになりました。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/dialog-close-event.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。

追記

ダイアログをクローズしたときに、フォームの複数のページ・アイテムの値を元のページに戻す実装を追加してみました。

フォームのページにあるプロセスダイアログを閉じる設定戻すアイテムに、値を戻すページ・アイテムをカンマ区切りで複数設定します。今回の例ではP2_ENAME,P2_JOBとしています。


レポートのページに、フォームから戻されたページ・アイテムの値を保持するページ・アイテムP1_ENAMEP1_JOBを作成しておきます。


イベントがダイアログのクローズのときに実行されるTRUEアクションを作成します。

アクション値の設定設定タイプの設定としてDialog Return Itemを選択します。戻りアイテムP2_ENAME(フォームのページにあるページ・アイテム名)を指定します。

影響を受ける要素選択タイプアイテムを選び、アイテムとしてフォームから戻される値を設定するページ・アイテムP1_ENAMEを指定します。


戻すアイテムが複数ある場合は、TRUEアクションの値の設定を追加します。


2023年9月8日金曜日

Oracle JETのGanttチャートを直接Oracle APEXで操作する

Oracle APEXのチャートのひとつにGanttチャートが含まれています。このチャートにはOracle JETのGanttチャートが使われていますが、使用できる機能はかなり限定されています。

Oracle JETのGanttチャートを(Oracle APEXのチャートとしてではなく)、直接APEXアプリケーションに実装してみます。理論上はOracle JETのGanttチャートが提供している機能をすべて利用できるようになります。

作成したアプリケーションは以下のように動作します。


Oracle JET Cookbookで紹介されているGanttのOverviewを実装しています。
https://www.oracle.com/webfolder/technetwork/jet/jetcookbook.html?component=gantt&demo=overview


いくつかのテキストやアイコンが表示されていません。CSS変数の定義の違いに影響されているのだと思われますが、原因については調べていません。

以下より実装について説明します。

Oracle JET CookbookではGanttチャートの表示に使用するデータを、rowData.jsonとdepData.jsonの2つの静的ファイルから読み込んでいます。このデータについては、データベースに表を作成してデータを投入します。

以下のDDLを実行し、表JGANTT_ROWSJGANTT_TASKSJGANTT_REFERENCE_OBJECTSJGANTT_DEPENDENCIESの4つの表を作成します。

SQLワークショップSQLスクリプトから実行します。


表が作成されたら、データをロードします。

ネットワーク経由でrowData.jsonを取得し、JSONをパースして表に投入します。以下のスクリプトを実行します。


SQLワークショップSQLコマンドから実行します。


同様にdepData.jsonを取得し、表にロードします。



続いて反対に、表に保存されているデータをJSON形式で取り出すRESTサービスを作成します。返されるデータが大きいため、Ajaxコールバックでは実装できません。

モジュール・ベース・パス/jgantt/URLテンプレートoverviewメソッドGETを指定して、リソース・ハンドラを作成します。

ソース・タイプとしてPL/SQLを選択し、ソースに以下を記述します。

APEXアプリケーションからは完全なURLを呼び出してデータを取得するため、このURLを記録しておきます。


アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。名前JET Native Ganttとします。作成されたアプリケーションのホーム・ページをページ・デザイナで開きます。

Breadcrumb BarにあるリージョンJET Native Ganttを削除します。その後にBodyに新規にリージョンを作成します。

識別タイトルGanttタイプととして静的コンテンツを選択します。ソースHTMLコードに以下を記述します。これはOracle JET Cookbookのdemo.htmlとほぼ同じで、<head>...</head>を取り除いています。

外観テンプレートとして、Blank with Attributes (No Grid)を選択しています。


ページ・プロパティJavaScriptファイルURLに以下を記述します。

[require jet]

ファンクションおよびグローバル変数の宣言に以下を記述します。

var view;

ページ・ロード時に実行に以下を記述します。

CSSのファイルURLに以下を記述します。

#JET_CSS_DIRECTORY#redwood/oj-redwood-notag-min.css

CSSインラインに以下を記述します。本来はもっと調整が必要な内容です。



JSONデータを返すRESTサービスのURLは、アプリケーション定義に置換文字列G_DATA_URLとして定義します。


以上でアプリケーションは完成です。アプリケーションを実行すると、記事の先頭のGIF動画のように動作します。

Oracle JET自体はオープンソースのJavaScriptライブラリであり、利用に当たって費用が発生しないかわりに、開発の主体であるオラクルからサポートを受けることもできません。Oracle APEXでは、APEXが使用している範囲であればOracle JETについてもSRを受け付けますが、Oracle JETをサポートしているわけではありません。

世の中には、Ganttチャートを実装したJavaScriptライブラリがいくつがあります。例えばAnyChartのAnyGanttやHighchartsのHighcarts Ganttなどです。これらは有償ですが、技術サポートが含まれています。単純に無料で使えるのでOracle JETを使って実装しようと考える前に、サポートが付いている商用のライブラリの使用を検討しても良いかと思います。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/jet-native-gantt.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。