foreman_remote_execution 17.2.3 → 18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +7 -7
  3. data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +27 -27
  4. data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +8 -8
  5. data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +1 -1
  6. data/app/controllers/api/v2/foreign_input_sets_controller.rb +4 -0
  7. data/app/models/remote_execution_provider.rb +1 -1
  8. data/db/seeds.d/60-ssh_proxy_feature.rb +2 -2
  9. data/db/seeds.d/90-bookmarks.rb +1 -1
  10. data/db/seeds.d/95-mail_notifications.rb +1 -1
  11. data/lib/foreman_remote_execution/plugin.rb +1 -1
  12. data/lib/foreman_remote_execution/version.rb +1 -1
  13. data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  14. data/locale/fr/foreman_remote_execution.po +7 -7
  15. data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  16. data/locale/ja/foreman_remote_execution.po +27 -27
  17. data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  18. data/locale/ko/foreman_remote_execution.po +8 -8
  19. data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  20. data/locale/zh_CN/foreman_remote_execution.po +1 -1
  21. data/test/unit/remote_execution_provider_test.rb +1 -1
  22. data/webpack/JobInvocationDetail/JobInvocationConstants.js +10 -0
  23. data/webpack/JobInvocationDetail/JobInvocationHostTable.js +8 -1
  24. data/webpack/JobInvocationDetail/__tests__/JobInvocationHostTablePolling.test.js +113 -7
  25. data/webpack/JobInvocationDetail/__tests__/areAllHostsTerminal.test.js +40 -0
  26. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +191 -212
  27. metadata +3 -8
@@ -195,7 +195,7 @@ msgid "Cancel job invocation or matching tasks only"
195
195
  msgstr "ジョブ呼び出しまたは一致するタスクのみをキャンセルします"
196
196
 
197
197
  msgid "Cancel recurring"
198
- msgstr "繰り返しをキャンセルする"
198
+ msgstr "繰り返しのキャンセル"
199
199
 
200
200
  msgid "Cancel selected"
201
201
  msgstr "選択項目をキャンセル"
@@ -378,13 +378,13 @@ msgid "Default SSH password"
378
378
  msgstr "デフォルトの SSH パスワード"
379
379
 
380
380
  msgid "Default key passphrase to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_key_passphrase"
381
- msgstr "SSH に使用するデフォルトの鍵パスフレーズ。remote_execution_ssh_key_passphrase という名前のパラメーターを設定することにより、ホストごとにオーバーライドできます"
381
+ msgstr "SSH に使用するデフォルトの鍵パスフレーズ。remote_execution_ssh_key_passphrase という名前のパラメーターを設定することにより、ホストごとに上書きできます"
382
382
 
383
383
  msgid "Default password to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_password"
384
- msgstr "SSH に使用するデフォルトパスワード。remote_execution_ssh_password という名前のパラメーターを設定することにより、ホストごとにオーバーライドできます"
384
+ msgstr "SSH に使用するデフォルトパスワード。remote_execution_ssh_password という名前のパラメーターを設定することにより、ホストごとに上書きできます"
385
385
 
386
386
  msgid "Default user to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_user."
387
- msgstr "SSH に使用するデフォルトユーザー。remote_execution_ssh_user という名前のパラメーターを設定することにより、ホストごとにオーバーライドできます。"
387
+ msgstr "SSH に使用するデフォルトユーザー。remote_execution_ssh_user という名前のパラメーターを設定することにより、ホストごとに上書きできます。"
388
388
 
389
389
  msgid "Default user to use for executing the script. If the user differs from the SSH user, su or sudo is used to switch the user."
390
390
  msgstr "スクリプトを実行するために使用するデフォルトユーザー。ユーザーが SSH ユーザーと異なる場合は、su または sudo を使用してユーザーを切り替えます。"
@@ -408,7 +408,7 @@ msgid "Designation of a special purpose"
408
408
  msgstr "特別な目的の指定"
409
409
 
410
410
  msgid "Disable recurring"
411
- msgstr "繰り返しを無効化する"
411
+ msgstr "繰り返しの無効化"
412
412
 
413
413
  msgid "Display advanced fields"
414
414
  msgstr "詳細フィールドを表示"
@@ -468,7 +468,7 @@ msgid "Enable Global Proxy"
468
468
  msgstr "グローバルプロキシーを有効にする"
469
469
 
470
470
  msgid "Enable recurring"
471
- msgstr "繰り返しを有効化する"
471
+ msgstr "繰り返しの有効化"
472
472
 
473
473
  msgid "End time needs to be after start time"
474
474
  msgstr "終了時刻は開始時刻の後でなければなりません"
@@ -828,7 +828,7 @@ msgid "Manual selection"
828
828
  msgstr "手動選択"
829
829
 
830
830
  msgid "Minute can only be a number between 0-59"
831
- msgstr "分は 0 から 59 までの数値である必要があります"
831
+ msgstr "分は 0-59 の間の数字のみ指定できます。"
832
832
 
833
833
  msgid "Missing the required permissions: ${missingPermissions.join( ', ' )}"
834
834
  msgstr "必須パーミッションがありません: ${missingPermissions.join( ', ' )}"
@@ -861,10 +861,10 @@ msgid "Next occurrence"
861
861
  msgstr "次の実行"
862
862
 
863
863
  msgid "No (override)"
864
- msgstr "No (オーバーライド)"
864
+ msgstr "No (上書き)"
865
865
 
866
866
  msgid "No Results"
867
- msgstr "該当する結果がありません"
867
+ msgstr "結果なし"
868
868
 
869
869
  msgid "No Target Hosts"
870
870
  msgstr "ターゲットホストがありません"
@@ -882,7 +882,7 @@ msgid "No output for the selected filters"
882
882
  msgstr "選択したフィルターの出力はありません"
883
883
 
884
884
  msgid "No results found"
885
- msgstr "該当する結果が見つかりませんでした"
885
+ msgstr "結果は見つかりませんでした"
886
886
 
887
887
  msgid "No template mapped to feature %{feature_name}"
888
888
  msgstr "機能 %{feature_name} にマッピングされたテンプレートがありません"
@@ -930,19 +930,19 @@ msgid "Open selected in new tab"
930
930
  msgstr "選択項目を新規タブで開きます"
931
931
 
932
932
  msgid "Opening job invocation form"
933
- msgstr "ジョブ呼び出しフォームを開いています"
933
+ msgstr "ジョブ呼び出しフォームを開く"
934
934
 
935
935
  msgid "Organization"
936
936
  msgstr "組織"
937
937
 
938
938
  msgid "Override the description format from the template for this invocation only"
939
- msgstr "この呼び出しに対してのみテンプレートから説明形式をオーバーライド"
939
+ msgstr "この呼び出しに対してのみテンプレートから説明形式を上書き"
940
940
 
941
941
  msgid "Override the global time to pickup interval for this invocation only"
942
- msgstr "グローバル時間をこの呼び出しだけの取得間隔にオーバーライドする"
942
+ msgstr "グローバル時間をこの呼び出しだけの取得間隔に上書きする"
943
943
 
944
944
  msgid "Override the timeout interval from the template for this invocation only"
945
- msgstr "この呼び出しに対してのみテンプレートからタイムアウト間隔をオーバーライド"
945
+ msgstr "この呼び出しに対してのみテンプレートからタイムアウト間隔を上書き"
946
946
 
947
947
  msgid "Overview"
948
948
  msgstr "概要"
@@ -975,7 +975,7 @@ msgid "Please enter a search query"
975
975
  msgstr "検索クエリーを入力してください"
976
976
 
977
977
  msgid "Please go back to \\\"Schedule\\\" - \\\"Future execution\\\" or \\\"Recurring execution\\\" step to fix the error"
978
- msgstr "エラーを修正するには、スケジュール - 後で実行または繰り返し実行の手順に戻ってください"
978
+ msgstr "エラーを修正するには、スケジュール - 後で実行または定期的な実行の手順に戻ってください。"
979
979
 
980
980
  msgid "Please go back to \\\"Schedule\\\" - \\\"Future execution\\\" step to fix the error"
981
981
  msgstr "「スケジュール」- 「後で実行」の手順に戻り、エラーを修正してください"
@@ -993,7 +993,7 @@ msgid "Please select at least one host"
993
993
  msgstr "ホストを 1 つ以上選択してください"
994
994
 
995
995
  msgid "Please select at least one host collection"
996
- msgstr "ホストコレクションを少なくとも 1 つ選択してください"
996
+ msgstr "ホストコレクションを少なくとも 1 つ選択してください。"
997
997
 
998
998
  msgid "Please select at least one host group"
999
999
  msgstr "少なくとも 1 つのホストグループを選択してください"
@@ -1002,7 +1002,7 @@ msgid "Popups are blocked by your browser. Please allow popups for this site to
1002
1002
  msgstr "ブラウザーでポップアップがブロックされています。このサイトのすべての呼び出しを新しいタブで開くには、ポップアップを許可してください。"
1003
1003
 
1004
1004
  msgid "Port to use for SSH communication. Default port 22. You may override per host by setting a parameter called remote_execution_ssh_port."
1005
- msgstr "SSH 通信に使用するポート。デフォルトのポートは 22 です。remote_execution_ssh_port という名前のパラメーターを設定することにより、ホストごとにオーバーライドできます。"
1005
+ msgstr "SSH 通信に使用するポート。デフォルトのポートは 22 です。remote_execution_ssh_port という名前のパラメーターを設定することにより、ホストごとに上書きできます。"
1006
1006
 
1007
1007
  msgid "Prefer IPv6 over IPv4"
1008
1008
  msgstr "IPv4 よりも IPv6 を優先する"
@@ -1071,16 +1071,16 @@ msgid "Recurring execution"
1071
1071
  msgstr "繰り返し実行"
1072
1072
 
1073
1073
  msgid "Recurring logic"
1074
- msgstr "繰り返し実行ロジック"
1074
+ msgstr "再帰論理"
1075
1075
 
1076
1076
  msgid "Recurring logic %s cancelled successfully."
1077
- msgstr "繰り返し実行ロジック %s が正常にキャンセルされました。"
1077
+ msgstr "再帰論理 %s が正常にキャンセルされました。"
1078
1078
 
1079
1079
  msgid "Recurring logic %s disabled successfully."
1080
- msgstr "繰り返し実行ロジック %s が正常に無効化されました。"
1080
+ msgstr "再帰論理 %s が正常に無効になりました。"
1081
1081
 
1082
1082
  msgid "Recurring logic %s enabled successfully."
1083
- msgstr "繰り返し実行ロジック %s が正常に有効化されました。"
1083
+ msgstr "再帰論理 %s が正常に有効になりました。"
1084
1084
 
1085
1085
  msgid "Recursive rendering of templates detected"
1086
1086
  msgstr "検出されたテンプレートの再帰的なレンダリング"
@@ -1311,7 +1311,7 @@ msgid "Setup remote execution pull mode. If set to `Yes`, pull provider client w
1311
1311
  msgstr "リモート実行のプルモードを設定します。`はい` に設定すると、登録したホストにプルプロパイダークライアントがデプロイされます。継承される値は、`host_registration_remote_execution_pull` パラメーターに基づきます。これは、ホストグループ、オペレーティングシステム、組織などから継承できます。オーバーライドされると、選択した値がホストパラメーターレベルで保存されます。"
1312
1312
 
1313
1313
  msgid "Should the ip addresses on host interfaces be preferred over the fqdn? It is useful when DNS not resolving the fqdns properly. You may override this per host by setting a parameter called remote_execution_connect_by_ip. For dual-stacked hosts you should consider the remote_execution_connect_by_ip_prefer_ipv6 setting"
1314
- msgstr "ホストインターフェイスの ip アドレスは fqdn よりも優先されますか? DNS が fqdn を適切に解決しない場合、これは役に立ちます。remote_execution_connect_by_ip というパラメーターを設定し、ホストごとにこれをオーバーライドすることができます。デュアルスタックのホストの場合は、remote_execution_connect_by_ip_prefer_ipv6 設定を検討する必要があります"
1314
+ msgstr "ホストインターフェイスの ip アドレスは fqdn よりも優先されますか? DNS が fqdn を適切に解決しない場合、これは役に立ちます。remote_execution_connect_by_ip というパラメーターを設定し、ホストごとにこれを上書きすることができます。デュアルスタックのホストの場合は、remote_execution_connect_by_ip_prefer_ipv6 設定を検討する必要があります"
1315
1315
 
1316
1316
  msgid "Should this interface be used for remote execution?"
1317
1317
  msgstr "このインターフェイスをリモート実行に使用する必要がありますか?"
@@ -1380,7 +1380,7 @@ msgid "Static query"
1380
1380
  msgstr "静的クエリー"
1381
1381
 
1382
1382
  msgid "Status"
1383
- msgstr "状態"
1383
+ msgstr "ステータス"
1384
1384
 
1385
1385
  msgid "Submit"
1386
1386
  msgstr "送信"
@@ -1720,16 +1720,16 @@ msgid "What user should be used to run the script (using sudo-like mechanisms).
1720
1720
  msgstr "スクリプトを実行するために使用するユーザー (sudo と同様のメカニズムを使用)。デフォルト値は、テンプレートパラメーターまたはグローバル設定です。"
1721
1721
 
1722
1722
  msgid "When connecting using ip address, should the IPv6 addresses be preferred? If no IPv6 address is set, it falls back to IPv4 automatically. You may override this per host by setting a parameter called remote_execution_connect_by_ip_prefer_ipv6. By default and for compatibility, IPv4 will be preferred over IPv6 by default"
1723
- msgstr "ip address を使用して接続する場合は、IPv6 アドレスを優先する必要がありますか? IPv6 アドレスが設定されていない場合は、IPv4 に自動的にフォールバックします。remote_execution_connect_by_ip_prefer_ipv6 というパラメーターを設定し、ホストごとにこれをオーバーライドすることができます。互換性のために、デフォルトでは IPv4 が IPv6 よりも優先されます"
1723
+ msgstr "ip address を使用して接続する場合は、IPv6 アドレスを優先する必要がありますか? IPv6 アドレスが設定されていない場合は、IPv4 に自動的にフォールバックします。remote_execution_connect_by_ip_prefer_ipv6 というパラメーターを設定し、ホストごとにこれを上書きすることができます。互換性のために、デフォルトでは IPv4 が IPv6 よりも優先されます"
1724
1724
 
1725
1725
  msgid "When enabled, working directories will be removed after task completion. You may override this per host by setting a parameter called remote_execution_cleanup_working_dirs."
1726
- msgstr "有効にすると、作業ディレクトリーはタスクの完了後に削除されます。remote_execution_cleanup_working_dirs と呼ばれるパラメーターを設定して、ホストごとにこれをオーバーライドすることができます。"
1726
+ msgstr "有効にすると、作業ディレクトリーはタスクの完了後に削除されます。remote_execution_cleanup_working_dirs と呼ばれるパラメーターを設定して、ホストごとにこれを上書きすることができます。"
1727
1727
 
1728
1728
  msgid "Where to find the Cockpit instance for the Web Console button. By default, no button is shown."
1729
1729
  msgstr "Web コンソールボタンの Cockpit インスタンスを検索する場所。デフォルトでは、ボタンは表示されません。"
1730
1730
 
1731
1731
  msgid "Whether it should be allowed to override the effective user from the invocation form."
1732
- msgstr "呼び出し形式から実効ユーザーをオーバーライドするのを許可するかどうか。"
1732
+ msgstr "呼び出し形式から実効ユーザーを上書きするのを許可するかどうか。"
1733
1733
 
1734
1734
  msgid "Whether or not the template is locked for editing"
1735
1735
  msgstr "テンプレートの編集機能をロックするかどうか"
@@ -1744,7 +1744,7 @@ msgid "Whether we should sync templates from disk when running db:seed."
1744
1744
  msgstr "db:seed を実行するときにディスクからテンプレートを同期するかどうか。"
1745
1745
 
1746
1746
  msgid "Yes (override)"
1747
- msgstr "Yes (オーバーライド)"
1747
+ msgstr "Yes (上書き)"
1748
1748
 
1749
1749
  msgid "You are not allowed to see the currently assigned template. Saving the form now would unassign the template."
1750
1750
  msgstr "現在割り当てられているテンプレートを参照できません。フォームを保存すると、テンプレートの割り当てが解除されます。"
@@ -360,7 +360,7 @@ msgid "Daily"
360
360
  msgstr "매일"
361
361
 
362
362
  msgid "Date must be in the future"
363
- msgstr "날짜는 미래 시점이어야 합니다."
363
+ msgstr "날짜는 미래여야 합니다."
364
364
 
365
365
  msgid "Days"
366
366
  msgstr "일"
@@ -423,7 +423,7 @@ msgid "Dynamic query"
423
423
  msgstr "동적 쿼리"
424
424
 
425
425
  msgid "Edit %s"
426
- msgstr "%s 편집"
426
+ msgstr "%s 편집 "
427
427
 
428
428
  msgid "Edit Job Template"
429
429
  msgstr "작업 템플릿 편집"
@@ -837,7 +837,7 @@ msgid "Must select a bookmark or enter a search query"
837
837
  msgstr "북마크를 선택하거나 검색 쿼리를 입력해야 합니다."
838
838
 
839
839
  msgid "N/A"
840
- msgstr "해당 없음"
840
+ msgstr "해당 없음 "
841
841
 
842
842
  msgid "Name"
843
843
  msgstr "이름 "
@@ -1341,7 +1341,7 @@ msgid "Smart proxy"
1341
1341
  msgstr "스마트 프록시"
1342
1342
 
1343
1343
  msgid "Snippet"
1344
- msgstr "스니펫"
1344
+ msgstr "조각 모음 "
1345
1345
 
1346
1346
  msgid "Start"
1347
1347
  msgstr "시작"
@@ -1377,7 +1377,7 @@ msgid "Static query"
1377
1377
  msgstr "정적 쿼리"
1378
1378
 
1379
1379
  msgid "Status"
1380
- msgstr "상태"
1380
+ msgstr "상태 "
1381
1381
 
1382
1382
  msgid "Submit"
1383
1383
  msgstr "보내기"
@@ -1525,7 +1525,7 @@ msgid "This can happen if the host is removed or moved to another organization o
1525
1525
  msgstr "작업이 시작된 후 호스트가 제거되거나 다른 조직이나 위치로 이동된 경우 이러한 문제가 발생할 수 있습니다."
1526
1526
 
1527
1527
  msgid "This template is locked for editing."
1528
- msgstr "이 템플릿은 편집이 잠금되어 있습니다. "
1528
+ msgstr "이 템플릿은 편집 용으로 잠금되어 있습니다. "
1529
1529
 
1530
1530
  msgid "This template is locked. Please clone it to a new template to customize."
1531
1531
  msgstr "이 템플릿은 잠겨 있습니다. 사용자 정의하려면 이를 새 템플릿에 복제하십시오. "
@@ -1636,7 +1636,7 @@ msgid "Unable to save template. Correct highlighted errors"
1636
1636
  msgstr "템플릿을 저장할 수 없습니다. 강조된 오류를 수정합니다."
1637
1637
 
1638
1638
  msgid "Unknown"
1639
- msgstr "알 수 없음"
1639
+ msgstr "알 수 없음 "
1640
1640
 
1641
1641
  msgid "Unknown execution status"
1642
1642
  msgstr "알 수 없는 실행 상태"
@@ -1729,7 +1729,7 @@ msgid "Whether it should be allowed to override the effective user from the invo
1729
1729
  msgstr "호출할 유효 사용자를 덮어쓰도록 허용할지 여부입니다."
1730
1730
 
1731
1731
  msgid "Whether or not the template is locked for editing"
1732
- msgstr "템플릿 편집 잠금 여부"
1732
+ msgstr "편집을 위한 템플릿의 잠금 여부 "
1733
1733
 
1734
1734
  msgid "Whether the current user login should be used as the effective user"
1735
1735
  msgstr "현재 사용자 로그인을 유효 사용자로 사용할지 여부입니다."
@@ -1074,7 +1074,7 @@ msgid "Recurring logic"
1074
1074
  msgstr "重复逻辑"
1075
1075
 
1076
1076
  msgid "Recurring logic %s cancelled successfully."
1077
- msgstr "成功取消循环逻辑 %s。"
1077
+ msgstr "成功取消重复逻辑 %s。"
1078
1078
 
1079
1079
  msgid "Recurring logic %s disabled successfully."
1080
1080
  msgstr "成功禁用重复逻辑 %s。"
@@ -125,7 +125,7 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
125
125
  describe 'effective user' do
126
126
  it 'takes the effective user from value from the template invocation' do
127
127
  template_invocation.effective_user = 'my user'
128
- assert_equal 'my\\ user', proxy_options[:effective_user]
128
+ assert_equal 'my user', proxy_options[:effective_user]
129
129
  end
130
130
  end
131
131
 
@@ -55,6 +55,16 @@ export const STATUS_TITLES = {
55
55
  NOT_STARTED: { id: 'N/A', title: __('Scheduled') },
56
56
  };
57
57
 
58
+ export const TERMINAL_HOST_STATUSES = new Set([
59
+ 'success',
60
+ 'error',
61
+ 'cancelled',
62
+ ]);
63
+
64
+ export const areAllHostsTerminal = results =>
65
+ results?.length > 0 &&
66
+ results.every(host => TERMINAL_HOST_STATUSES.has(host.job_status));
67
+
58
68
  export const DATE_OPTIONS = {
59
69
  day: 'numeric',
60
70
  month: 'short',
@@ -42,6 +42,7 @@ import Columns, {
42
42
  STATUS_UPPERCASE,
43
43
  AWAITING_STATUS_FILTER,
44
44
  AUTO_REFRESH_INTERVAL_MS,
45
+ areAllHostsTerminal,
45
46
  } from './JobInvocationConstants';
46
47
  import { TemplateInvocation } from './TemplateInvocation';
47
48
  import { RowActions } from './TemplateInvocationComponents/TemplateActionButtons';
@@ -196,7 +197,13 @@ const JobInvocationHostTable = ({
196
197
  if (thisRequest !== requestIdRef.current) return;
197
198
  if (!mountedRef.current) return;
198
199
  updateHostsState(data);
199
- if (!jobFinishedRef.current) {
200
+ const hasActiveFilter = !!(
201
+ currentPollParams.current.search ||
202
+ currentPollParams.current.awaiting
203
+ );
204
+ const pageAllTerminal =
205
+ !hasActiveFilter && areAllHostsTerminal(data.data?.results);
206
+ if (!jobFinishedRef.current && !pageAllTerminal) {
200
207
  pollTimeoutId.current = setTimeout(
201
208
  () => makeApiCall(currentPollParams.current),
202
209
  AUTO_REFRESH_INTERVAL_MS
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  import React from 'react';
2
3
  import { render, act } from '@testing-library/react';
3
4
  import '@testing-library/jest-dom';
@@ -44,13 +45,28 @@ const hostsResponse = {
44
45
  operatingsystem_name: 'RHEL 9',
45
46
  hostgroup_id: 1,
46
47
  hostgroup_name: 'default',
47
- job_status: 'success',
48
+ job_status: 'running',
48
49
  smart_proxy_id: 1,
49
50
  smart_proxy_name: 'proxy1',
50
51
  },
51
52
  ],
52
53
  };
53
54
 
55
+ const terminalHostsResponse = {
56
+ ...hostsResponse,
57
+ results: [{ ...hostsResponse.results[0], job_status: 'success' }],
58
+ };
59
+
60
+ const mixedHostsResponse = {
61
+ ...hostsResponse,
62
+ total: 2,
63
+ subtotal: 2,
64
+ results: [
65
+ { ...hostsResponse.results[0], id: 1, job_status: 'success' },
66
+ { ...hostsResponse.results[0], id: 2, job_status: 'running' },
67
+ ],
68
+ };
69
+
54
70
  let apiGetSpy;
55
71
  let hostsCalls;
56
72
  let pendingCallbacks;
@@ -66,9 +82,9 @@ const flushPendingCallbacks = () => {
66
82
  batch.forEach(cb => cb());
67
83
  };
68
84
 
69
- const renderTable = (props = {}) => {
85
+ const renderTable = (props = {}, historyEntries = ['/']) => {
70
86
  const store = createStore();
71
- const history = createMemoryHistory();
87
+ const history = createMemoryHistory({ initialEntries: historyEntries });
72
88
  const Wrapper = createForemanContextWrapper();
73
89
 
74
90
  const defaultProps = {
@@ -93,16 +109,14 @@ const renderTable = (props = {}) => {
93
109
  return { ...result, store, history };
94
110
  };
95
111
 
96
- const setupSuccessMock = () => {
112
+ const setupSuccessMock = (response = hostsResponse) => {
97
113
  apiGetSpy = jest
98
114
  .spyOn(APIActions, 'get')
99
115
  .mockImplementation(opts => dispatch => {
100
116
  if (opts.key === JOB_INVOCATION_HOSTS) {
101
117
  hostsCalls.push(opts);
102
118
  if (opts.handleSuccess) {
103
- pendingCallbacks.push(() =>
104
- opts.handleSuccess({ data: hostsResponse })
105
- );
119
+ pendingCallbacks.push(() => opts.handleSuccess({ data: response }));
106
120
  }
107
121
  }
108
122
  });
@@ -311,6 +325,98 @@ describe('JobInvocationHostTable polling', () => {
311
325
  expect(hostsCalls.length).toBeGreaterThan(callsAfterFilterChange);
312
326
  });
313
327
 
328
+ it('stops polling when all hosts on the page are terminal', () => {
329
+ apiGetSpy.mockRestore();
330
+ setupSuccessMock(terminalHostsResponse);
331
+
332
+ renderTable();
333
+
334
+ expect(hostsCalls).toHaveLength(1);
335
+
336
+ act(() => {
337
+ flushPendingCallbacks();
338
+ });
339
+
340
+ act(() => {
341
+ jest.advanceTimersByTime(10000);
342
+ });
343
+
344
+ act(() => {
345
+ flushPendingCallbacks();
346
+ });
347
+
348
+ expect(hostsCalls).toHaveLength(1);
349
+ });
350
+
351
+ it('continues polling when at least one host on the page is non-terminal', () => {
352
+ apiGetSpy.mockRestore();
353
+ setupSuccessMock(mixedHostsResponse);
354
+
355
+ renderTable();
356
+
357
+ expect(hostsCalls).toHaveLength(1);
358
+
359
+ act(() => {
360
+ flushPendingCallbacks();
361
+ });
362
+
363
+ act(() => {
364
+ jest.advanceTimersByTime(5000);
365
+ });
366
+
367
+ act(() => {
368
+ flushPendingCallbacks();
369
+ });
370
+
371
+ expect(hostsCalls).toHaveLength(2);
372
+ });
373
+
374
+ it('continues polling when all hosts on the page are terminal but a status filter is active', () => {
375
+ apiGetSpy.mockRestore();
376
+ setupSuccessMock(terminalHostsResponse);
377
+
378
+ renderTable({ initialFilter: 'success' });
379
+
380
+ expect(hostsCalls).toHaveLength(1);
381
+
382
+ act(() => {
383
+ flushPendingCallbacks();
384
+ });
385
+
386
+ act(() => {
387
+ jest.advanceTimersByTime(5000);
388
+ });
389
+
390
+ act(() => {
391
+ flushPendingCallbacks();
392
+ });
393
+
394
+ expect(hostsCalls).toHaveLength(2);
395
+ });
396
+
397
+ it('continues polling when all hosts on the page are terminal but a search query is active', () => {
398
+ apiGetSpy.mockRestore();
399
+ setupSuccessMock(terminalHostsResponse);
400
+
401
+ renderTable({}, ['/?search=host1']);
402
+
403
+ expect(hostsCalls).toHaveLength(1);
404
+
405
+ act(() => {
406
+ flushPendingCallbacks();
407
+ });
408
+
409
+ act(() => {
410
+ jest.advanceTimersByTime(5000);
411
+ });
412
+
413
+ act(() => {
414
+ flushPendingCallbacks();
415
+ });
416
+
417
+ expect(hostsCalls).toHaveLength(2);
418
+ });
419
+
314
420
  it('sends include_permissions only on the first request', () => {
315
421
  renderTable();
316
422
 
@@ -0,0 +1,40 @@
1
+ import { areAllHostsTerminal } from '../JobInvocationConstants';
2
+
3
+ describe('areAllHostsTerminal', () => {
4
+ it('returns false for an empty results array', () => {
5
+ expect(areAllHostsTerminal([])).toBe(false);
6
+ });
7
+
8
+ it('returns false for undefined results', () => {
9
+ expect(areAllHostsTerminal(undefined)).toBe(false);
10
+ });
11
+
12
+ it('returns true when every host is in a terminal state', () => {
13
+ expect(
14
+ areAllHostsTerminal([
15
+ { id: 1, job_status: 'success' },
16
+ { id: 2, job_status: 'error' },
17
+ { id: 3, job_status: 'cancelled' },
18
+ ])
19
+ ).toBe(true);
20
+ });
21
+
22
+ it('returns false when at least one host is non-terminal', () => {
23
+ expect(
24
+ areAllHostsTerminal([
25
+ { id: 1, job_status: 'success' },
26
+ { id: 2, job_status: 'running' },
27
+ ])
28
+ ).toBe(false);
29
+ });
30
+
31
+ it('returns false when every host is non-terminal', () => {
32
+ expect(
33
+ areAllHostsTerminal([
34
+ { id: 1, job_status: 'running' },
35
+ { id: 2, job_status: 'planned' },
36
+ { id: 3, job_status: 'N/A' },
37
+ ])
38
+ ).toBe(false);
39
+ });
40
+ });