Workunits.tsx 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import * as React from "react";
  2. import { CommandBar, ContextualMenuItemType, ICommandBarItemProps } from "@fluentui/react";
  3. import { scopedLogger } from "@hpcc-js/util";
  4. import * as domClass from "dojo/dom-class";
  5. import * as ESPWorkunit from "src/ESPWorkunit";
  6. import * as WsWorkunits from "src/WsWorkunits";
  7. import { formatCost } from "src/Session";
  8. import * as Utility from "src/Utility";
  9. import nlsHPCC from "src/nlsHPCC";
  10. import { useConfirm } from "../hooks/confirm";
  11. import { useGrid } from "../hooks/grid";
  12. import { useBuildInfo } from "../hooks/platform";
  13. import { HolyGrail } from "../layouts/HolyGrail";
  14. import { pushParams } from "../util/history";
  15. import { Fields } from "./forms/Fields";
  16. import { Filter } from "./forms/Filter";
  17. import { ShortVerticalDivider } from "./Common";
  18. import { selector } from "./DojoGrid";
  19. const logger = scopedLogger("src-react/components/Workunits.tsx");
  20. const FilterFields: Fields = {
  21. "Type": { type: "checkbox", label: nlsHPCC.ArchivedOnly },
  22. "Wuid": { type: "string", label: nlsHPCC.WUID, placeholder: "W20200824-060035" },
  23. "Owner": { type: "string", label: nlsHPCC.Owner, placeholder: nlsHPCC.jsmi },
  24. "Jobname": { type: "string", label: nlsHPCC.JobName, placeholder: nlsHPCC.log_analysis_1 },
  25. "Cluster": { type: "target-cluster", label: nlsHPCC.Cluster, placeholder: "" },
  26. "State": { type: "workunit-state", label: nlsHPCC.State, placeholder: "" },
  27. "ECL": { type: "string", label: nlsHPCC.ECL, placeholder: nlsHPCC.dataset },
  28. "LogicalFile": { type: "string", label: nlsHPCC.LogicalFile, placeholder: nlsHPCC.somefile },
  29. "LogicalFileSearchType": { type: "logicalfile-type", label: nlsHPCC.LogicalFileType, placeholder: "", disabled: (params: Fields) => !params.LogicalFile.value },
  30. "LastNDays": { type: "string", label: nlsHPCC.LastNDays, placeholder: "2" },
  31. "StartDate": { type: "datetime", label: nlsHPCC.FromDate, placeholder: "" },
  32. "EndDate": { type: "datetime", label: nlsHPCC.ToDate, placeholder: "" },
  33. };
  34. function formatQuery(_filter) {
  35. const filter = { ..._filter };
  36. if (filter.LastNDays) {
  37. const end = new Date();
  38. const start = new Date();
  39. start.setDate(end.getDate() - filter.LastNDays);
  40. filter.StartDate = start.toISOString();
  41. filter.EndDate = end.toISOString();
  42. delete filter.LastNDays;
  43. } else {
  44. if (filter.StartDate) {
  45. filter.StartDate = new Date(filter.StartDate).toISOString();
  46. }
  47. if (filter.EndDate) {
  48. filter.EndDate = new Date(filter.StartDate).toISOString();
  49. }
  50. }
  51. logger.debug(filter);
  52. return filter;
  53. }
  54. const defaultUIState = {
  55. hasSelection: false,
  56. hasProtected: false,
  57. hasNotProtected: false,
  58. hasFailed: false,
  59. hasNotFailed: false,
  60. hasCompleted: false,
  61. hasNotCompleted: false
  62. };
  63. interface WorkunitsProps {
  64. filter?: object;
  65. store?: any;
  66. }
  67. const emptyFilter = {};
  68. export const Workunits: React.FunctionComponent<WorkunitsProps> = ({
  69. filter = emptyFilter,
  70. store
  71. }) => {
  72. const [showFilter, setShowFilter] = React.useState(false);
  73. const [mine, setMine] = React.useState(false);
  74. const [uiState, setUIState] = React.useState({ ...defaultUIState });
  75. const [, { currencyCode }] = useBuildInfo();
  76. // Grid ---
  77. const [Grid, selection, refreshTable, copyButtons] = useGrid({
  78. store: store ? store : ESPWorkunit.CreateWUQueryStore({}),
  79. query: formatQuery(filter),
  80. sort: [{ attribute: "Wuid", "descending": true }],
  81. filename: "workunits",
  82. columns: {
  83. col1: selector({
  84. width: 27,
  85. selectorType: "checkbox"
  86. }),
  87. Protected: {
  88. renderHeaderCell: function (node) {
  89. node.innerHTML = Utility.getImageHTML("locked.png", nlsHPCC.Protected);
  90. },
  91. width: 25,
  92. sortable: false,
  93. formatter: function (_protected) {
  94. if (_protected === true) {
  95. return Utility.getImageHTML("locked.png");
  96. }
  97. return "";
  98. }
  99. },
  100. Wuid: {
  101. label: nlsHPCC.WUID, width: 180,
  102. formatter: function (Wuid, row) {
  103. const wu = ESPWorkunit.Get(Wuid);
  104. return `${wu.getStateImageHTML()}&nbsp;<a href='#/workunits/${Wuid}'>${Wuid}</a>`;
  105. }
  106. },
  107. Owner: { label: nlsHPCC.Owner, width: 90 },
  108. Jobname: { label: nlsHPCC.JobName, width: 350 },
  109. Cluster: { label: nlsHPCC.Cluster, width: 60 },
  110. RoxieCluster: { label: nlsHPCC.RoxieCluster, width: 90 },
  111. State: { label: nlsHPCC.State, width: 60 },
  112. TotalClusterTime: {
  113. label: nlsHPCC.TotalClusterTime, width: 115,
  114. renderCell: function (object, value, node) {
  115. domClass.add(node, "justify-right");
  116. node.innerText = value;
  117. }
  118. },
  119. ExecuteCost: {
  120. label: nlsHPCC.ExecuteCost, width: 100,
  121. formatter: function (cost, row) {
  122. return `${formatCost(cost ?? 0)} (${currencyCode || "$"})`;
  123. }
  124. },
  125. FileAccessCost: {
  126. label: nlsHPCC.FileAccessCost, width: 100,
  127. formatter: function (cost, row) {
  128. return `${formatCost(cost ?? 0)} (${currencyCode || "$"})`;
  129. }
  130. }
  131. }
  132. });
  133. const [DeleteConfirm, setShowDeleteConfirm] = useConfirm({
  134. title: nlsHPCC.Delete,
  135. message: nlsHPCC.DeleteSelectedWorkunits,
  136. items: selection.map(s => s.Wuid),
  137. onSubmit: React.useCallback(() => {
  138. WsWorkunits.WUAction(selection, "Delete").then(() => refreshTable(true));
  139. }, [refreshTable, selection])
  140. });
  141. // Filter ---
  142. const filterFields: Fields = {};
  143. for (const fieldID in FilterFields) {
  144. filterFields[fieldID] = { ...FilterFields[fieldID], value: filter[fieldID] };
  145. }
  146. // Command Bar ---
  147. const buttons = React.useMemo((): ICommandBarItemProps[] => [
  148. {
  149. key: "refresh", text: nlsHPCC.Refresh, iconProps: { iconName: "Refresh" },
  150. onClick: () => refreshTable()
  151. },
  152. { key: "divider_1", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
  153. {
  154. key: "open", text: nlsHPCC.Open, disabled: !uiState.hasSelection, iconProps: { iconName: "WindowEdit" },
  155. onClick: () => {
  156. if (selection.length === 1) {
  157. window.location.href = `#/workunits/${selection[0].Wuid}`;
  158. } else {
  159. for (let i = selection.length - 1; i >= 0; --i) {
  160. window.open(`#/workunits/${selection[i].Wuid}`, "_blank");
  161. }
  162. }
  163. }
  164. },
  165. {
  166. key: "delete", text: nlsHPCC.Delete, disabled: !uiState.hasNotProtected, iconProps: { iconName: "Delete" },
  167. onClick: () => setShowDeleteConfirm(true)
  168. },
  169. { key: "divider_2", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
  170. {
  171. key: "setFailed", text: nlsHPCC.SetToFailed, disabled: !uiState.hasNotProtected,
  172. onClick: () => { WsWorkunits.WUAction(selection, "SetToFailed"); }
  173. },
  174. {
  175. key: "abort", text: nlsHPCC.Abort, disabled: !uiState.hasNotCompleted,
  176. onClick: () => { WsWorkunits.WUAction(selection, "Abort"); }
  177. },
  178. { key: "divider_3", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
  179. {
  180. key: "protect", text: nlsHPCC.Protect, disabled: !uiState.hasNotProtected,
  181. onClick: () => { WsWorkunits.WUAction(selection, "Protect"); }
  182. },
  183. {
  184. key: "unprotect", text: nlsHPCC.Unprotect, disabled: !uiState.hasProtected,
  185. onClick: () => { WsWorkunits.WUAction(selection, "Unprotect"); }
  186. },
  187. { key: "divider_4", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
  188. {
  189. key: "filter", text: nlsHPCC.Filter, disabled: !!store, iconProps: { iconName: "Filter" },
  190. onClick: () => { setShowFilter(true); }
  191. },
  192. {
  193. key: "mine", text: nlsHPCC.Mine, disabled: true, iconProps: { iconName: "Contact" }, canCheck: true, checked: mine,
  194. onClick: () => { setMine(!mine); }
  195. },
  196. ], [mine, refreshTable, selection, setShowDeleteConfirm, store, uiState.hasNotCompleted, uiState.hasNotProtected, uiState.hasProtected, uiState.hasSelection]);
  197. // Selection ---
  198. React.useEffect(() => {
  199. const state = { ...defaultUIState };
  200. for (let i = 0; i < selection.length; ++i) {
  201. state.hasSelection = true;
  202. if (selection[i] && selection[i].Protected !== null) {
  203. if (selection[i].Protected !== false) {
  204. state.hasProtected = true;
  205. } else {
  206. state.hasNotProtected = true;
  207. }
  208. }
  209. if (selection[i] && selection[i].StateID !== null) {
  210. if (selection[i].StateID === 4) {
  211. state.hasFailed = true;
  212. } else {
  213. state.hasNotFailed = true;
  214. }
  215. if (WsWorkunits.isComplete(selection[i].StateID, selection[i].ActionEx)) {
  216. state.hasCompleted = true;
  217. } else {
  218. state.hasNotCompleted = true;
  219. }
  220. }
  221. }
  222. setUIState(state);
  223. }, [selection]);
  224. return <HolyGrail
  225. header={<CommandBar items={buttons} farItems={copyButtons} />}
  226. main={
  227. <>
  228. <Grid />
  229. <Filter showFilter={showFilter} setShowFilter={setShowFilter} filterFields={filterFields} onApply={pushParams} />
  230. <DeleteConfirm />
  231. </>
  232. }
  233. />;
  234. };