From e9494689027c96c08fe8990d945cf7f785a4696f Mon Sep 17 00:00:00 2001
From: jayesh hathila
Date: Tue, 13 Apr 2021 21:09:07 +0530
Subject: [PATCH 01/93] Reduce duplicate code in Rollup Pages (#167)
Move duplicate code to base class for Edit and Create Rollup pages.
---
.../BaseAggregationAndMetricSettings.tsx | 181 ++++++++++++++++++
.../HistogramAndMetrics.tsx | 167 ++++------------
.../AggregationAndMetricsSettings.tsx | 161 +++-------------
3 files changed, 246 insertions(+), 263 deletions(-)
create mode 100644 public/pages/Commons/BaseAggregationAndMetricSettings.tsx
diff --git a/public/pages/Commons/BaseAggregationAndMetricSettings.tsx b/public/pages/Commons/BaseAggregationAndMetricSettings.tsx
new file mode 100644
index 000000000..a0468f000
--- /dev/null
+++ b/public/pages/Commons/BaseAggregationAndMetricSettings.tsx
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, {Fragment} from "react";
+
+import {
+ EuiFlexItem,
+ EuiText,
+ EuiBasicTable,
+ EuiTableFieldDataColumnType,
+ EuiPanel,
+ EuiFlexGroup,
+ EuiIcon,
+} from "@elastic/eui";
+
+import {DimensionItem, MetricItem} from "../../../models/interfaces";
+
+export const AGGREGATION_AND_METRIC_SETTINGS = 'Aggregation and metrics settings'
+
+export interface BaseAggregationAndMetricsState {
+ from: number;
+ size: number;
+ sortField: string;
+ sortDirection: string;
+ dimensionFrom: number;
+ dimensionSize: number;
+ dimensionSortField: string;
+ dimensionSortDirection: string;
+}
+
+
+export const BaseAggregationColumns: Readonly>[] = [
+ {
+ field: "sequence",
+ name: "Sequence",
+ sortable: true,
+ align: "left",
+ dataType: "number",
+ },
+ {
+ field: "field.label",
+ name: "Field name",
+ align: "left",
+ },
+ {
+ field: "aggregationMethod",
+ name: "Aggregation method",
+ align: "left",
+ },
+ {
+ field: "interval",
+ name: "Interval",
+ dataType: "number",
+ align: "left",
+ render: (interval: null | number) => {
+ if (interval == null) return "-";
+ else return `${interval}`;
+ },
+ },
+];
+
+
+export const BaseMetricsColumns: Readonly>[] = [
+ {
+ field: "source_field",
+ name: "Field Name",
+ },
+ {
+ field: "min",
+ name: "Min",
+ align: "center",
+ render: (min: boolean) => min && ,
+ },
+ {
+ field: "max",
+ name: "Max",
+ align: "center",
+ render: (max: boolean) => max && ,
+ },
+ {
+ field: "sum",
+ name: "Sum",
+ align: "center",
+ render: (sum: boolean) => sum && ,
+ },
+ {
+ field: "avg",
+ name: "Avg",
+ align: "center",
+ render: (avg: boolean) => avg && ,
+ },
+ {
+ field: "value_count",
+ name: "Value count",
+ align: "center",
+ render: (value_count: boolean) => value_count && ,
+ },
+];
+
+export function sequenceTableComponents(selectedDimensionField, items, columns, pagination, sorting, onChange) {
+ if(selectedDimensionField.length == 0) {
+ return (
+
+ No fields added for aggregation
+
+ )
+ }
+
+ return (
+
+
+
+
+ )
+}
+
+export function additionalMetricsComponent(selectedMetrics) {
+ return (
+
+
+
+ Additional metrics
+
+
+
+
+ {`(${selectedMetrics.length})`}
+
+
+
+ )
+}
+
+export function sourceFieldComponents(selectedMetrics, items, columns, pagination, sorting, onChange) {
+
+ if(selectedMetrics.length == 0) {
+ return (
+
+ No fields added for metrics
+
+ )
+ }
+
+ return (
+
+
+
+
+
+ )
+}
diff --git a/public/pages/CreateRollup/components/HistogramAndMetrics/HistogramAndMetrics.tsx b/public/pages/CreateRollup/components/HistogramAndMetrics/HistogramAndMetrics.tsx
index 856339ee3..bd15b66e2 100644
--- a/public/pages/CreateRollup/components/HistogramAndMetrics/HistogramAndMetrics.tsx
+++ b/public/pages/CreateRollup/components/HistogramAndMetrics/HistogramAndMetrics.tsx
@@ -13,16 +13,14 @@
* permissions and limitations under the License.
*/
-import React, { Component, Fragment } from "react";
+import React, { Component } from "react";
import {
EuiComboBoxOptionOption,
EuiFlexGrid,
EuiFlexItem,
EuiSpacer,
EuiText,
- EuiBasicTable,
EuiTableFieldDataColumnType,
- EuiPanel,
EuiFlexGroup,
// @ts-ignore
Criteria,
@@ -36,6 +34,12 @@ import { ModalConsumer } from "../../../../components/Modal";
import { DEFAULT_PAGE_SIZE_OPTIONS } from "../../../Rollups/utils/constants";
import { parseTimeunit } from "../../utils/helpers";
import { DimensionItem, MetricItem } from "../../../../../models/interfaces";
+import {
+ additionalMetricsComponent,
+ AGGREGATION_AND_METRIC_SETTINGS,
+ BaseAggregationAndMetricsState,
+ BaseAggregationColumns, BaseMetricsColumns, sequenceTableComponents, sourceFieldComponents
+} from "../../../Commons/BaseAggregationAndMetricSettings";
interface HistogramAndMetricsProps {
rollupId: string;
@@ -49,97 +53,35 @@ interface HistogramAndMetricsProps {
selectedMetrics: MetricItem[];
}
-interface HistogramAndMetricsState {
- from: number;
- size: number;
- sortField: string;
- sortDirection: string;
+interface HistogramAndMetricsState extends BaseAggregationAndMetricsState {
metricsShown: MetricItem[];
- dimensionFrom: number;
- dimensionSize: number;
- dimensionSortField: string;
- dimensionSortDirection: string;
dimensionsShown: DimensionItem[];
}
-const aggregationColumns: EuiTableFieldDataColumnType[] = [
- {
- field: "sequence",
- name: "Sequence",
- sortable: true,
- align: "left",
- dataType: "number",
- },
- {
- field: "field.label",
- name: "Field name",
- align: "left",
- },
+
+const _createFlowAggregateColumns: Readonly>[] = [
{
field: "field.type",
name: "Field type",
align: "left",
render: (type) => (type == undefined ? "-" : type),
},
- {
- field: "aggregationMethod",
- name: "Aggregation method",
- align: "left",
- },
- {
- field: "interval",
- name: "Interval",
- dataType: "number",
- align: "left",
- render: (interval: null | number) => {
- if (interval == null) return "-";
- else return `${interval}`;
- },
- },
-];
+]
-const metricsColumns: EuiTableFieldDataColumnType[] = [
- {
- field: "source_field.label",
- name: "Field Name",
- },
- {
+const _createFlowMetricsColumn: Readonly> = {
field: "all",
name: "All",
align: "center",
render: (all: boolean) => all && ,
- },
- {
- field: "min",
- name: "Min",
- align: "center",
- render: (min: boolean) => min && ,
- },
- {
- field: "max",
- name: "Max",
- align: "center",
- render: (max: boolean) => max && ,
- },
- {
- field: "sum",
- name: "Sum",
- align: "center",
- render: (sum: boolean) => sum && ,
- },
- {
- field: "avg",
- name: "Avg",
- align: "center",
- render: (avg: boolean) => avg && ,
- },
- {
- field: "value_count",
- name: "Value count",
- align: "center",
- render: (value_count: boolean) => value_count && ,
- },
-];
+ }
+
+
+const aggregationColumns: Readonly>[]
+ = [...BaseAggregationColumns,..._createFlowAggregateColumns];
+
+// Adding 'all' column at 1st index.
+const metricsColumns: EuiTableFieldDataColumnType[] =
+ [...BaseMetricsColumns].splice(1, 0, _createFlowMetricsColumn);
export default class HistogramAndMetrics extends Component {
constructor(props: HistogramAndMetricsProps) {
@@ -260,7 +202,7 @@ export default class HistogramAndMetrics extends Component
}
bodyStyles={{ padding: "initial" }}
- title="Aggregation and metrics setting"
+ title={AGGREGATION_AND_METRIC_SETTINGS}
titleSize="m"
>
@@ -303,61 +245,22 @@ export default class HistogramAndMetrics extends Component
- {selectedDimensionField.length ? (
-
-
-
-
-
- ) : (
-
- No fields added for aggregation
-
- )}
+ {
+ sequenceTableComponents( selectedDimensionField, dimensionsShown, aggregationColumns,
+ dimensionPagination, dimensionSorting, this.onDimensionTableChange )
+ }
+
-
-
-
- Additional metrics
-
-
-
-
- {`(${selectedMetrics.length})`}
-
-
-
- {selectedMetrics.length ? (
-
-
-
-
-
- ) : (
-
- No fields added for metrics
-
- )}
+ {
+ additionalMetricsComponent(selectedMetrics)
+ }
+
+ {
+ sourceFieldComponents(selectedMetrics, metricsShown, metricsColumns, pagination,
+ sorting, this.onTableChange)
+ }
diff --git a/public/pages/RollupDetails/components/AggregationAndMetricsSettings/AggregationAndMetricsSettings.tsx b/public/pages/RollupDetails/components/AggregationAndMetricsSettings/AggregationAndMetricsSettings.tsx
index 37f4babfe..3051097ad 100644
--- a/public/pages/RollupDetails/components/AggregationAndMetricsSettings/AggregationAndMetricsSettings.tsx
+++ b/public/pages/RollupDetails/components/AggregationAndMetricsSettings/AggregationAndMetricsSettings.tsx
@@ -13,16 +13,13 @@
* permissions and limitations under the License.
*/
-import React, { Component, Fragment } from "react";
+import React, { Component } from "react";
import {
EuiFlexGrid,
EuiSpacer,
EuiFlexItem,
EuiText,
EuiFlexGroup,
- EuiPanel,
- EuiBasicTable,
- EuiIcon,
EuiTableFieldDataColumnType,
//@ts-ignore
Criteria,
@@ -34,6 +31,13 @@ import { ContentPanel } from "../../../../components/ContentPanel";
import { DEFAULT_PAGE_SIZE_OPTIONS } from "../../../Rollups/utils/constants";
import { parseTimeunit } from "../../../CreateRollup/utils/helpers";
import { DimensionItem, FieldItem, MetricItem } from "../../../../../models/interfaces";
+import {
+ additionalMetricsComponent,
+ AGGREGATION_AND_METRIC_SETTINGS,
+ BaseAggregationAndMetricsState,
+ BaseAggregationColumns,
+ BaseMetricsColumns, sequenceTableComponents, sourceFieldComponents
+} from "../../../Commons/BaseAggregationAndMetricSettings";
interface AggregationAndMetricsSettingsProps {
timestamp: string;
@@ -47,83 +51,12 @@ interface AggregationAndMetricsSettingsProps {
onChangeMetricsShown: (from: number, size: number) => void;
}
-interface AggregationAndMetricsSettingsState {
- from: number;
- size: number;
- sortField: string;
- sortDirection: string;
- dimensionFrom: number;
- dimensionSize: number;
- dimensionSortField: string;
- dimensionSortDirection: string;
+interface AggregationAndMetricsSettingsState extends BaseAggregationAndMetricsState {
}
-const aggregationColumns: EuiTableFieldDataColumnType[] = [
- {
- field: "sequence",
- name: "Sequence",
- sortable: true,
- align: "left",
- dataType: "number",
- },
- {
- field: "field.label",
- name: "Field name",
- align: "left",
- },
- {
- field: "aggregationMethod",
- name: "Aggregation method",
- align: "left",
- },
- {
- field: "interval",
- name: "Interval",
- dataType: "number",
- align: "left",
- render: (interval: null | number) => {
- if (interval == null) return "-";
- else return `${interval}`;
- },
- },
-];
+const aggregationColumns: Readonly>[] = BaseAggregationColumns;
-const metricsColumns = [
- {
- field: "source_field",
- name: "Field Name",
- },
- {
- field: "min",
- name: "Min",
- align: "center",
- render: (min: boolean) => min && ,
- },
- {
- field: "max",
- name: "Max",
- align: "center",
- render: (max: boolean) => max && ,
- },
- {
- field: "sum",
- name: "Sum",
- align: "center",
- render: (sum: boolean) => sum && ,
- },
- {
- field: "avg",
- name: "Avg",
- align: "center",
- render: (avg: boolean) => avg && ,
- },
- {
- field: "value_count",
- name: "Value count",
- align: "center",
- render: (value_count: boolean) => value_count && ,
- },
-];
+const metricsColumns = BaseMetricsColumns;
export default class AggregationAndMetricsSettings extends Component<
AggregationAndMetricsSettingsProps,
@@ -207,7 +140,11 @@ export default class AggregationAndMetricsSettings extends Component<
interval = intervalValue[0] + " " + parseTimeunit(intervalUnit[0]);
}
return (
-
+
@@ -247,61 +184,23 @@ export default class AggregationAndMetricsSettings extends Component<
- {selectedDimensionField.length ? (
-
-
-
-
-
- ) : (
-
- No fields added for aggregation
-
- )}
+ {
+ sequenceTableComponents(selectedDimensionField, dimensionsShown, aggregationColumns,
+ dimensionPagination, dimensionSorting, this.onDimensionTableChange)
+ }
+
-
-
-
- Additional metrics
-
-
-
-
- {`(${selectedMetrics.length})`}
-
-
-
- {selectedMetrics.length ? (
-
-
-
-
-
- ) : (
-
- No fields added for metrics
-
- )}
+
+ {
+ additionalMetricsComponent(selectedMetrics)
+ }
+
+ {
+ sourceFieldComponents(selectedMetrics, metricsShown, metricsColumns, pagination,
+ sorting, this.onTableChange)
+ }
From 067f3483cd48c6072695fb778e780c07cedc6682 Mon Sep 17 00:00:00 2001
From: Ravi Thaluru
Date: Mon, 19 Apr 2021 21:17:09 -0700
Subject: [PATCH 02/93] Initial commit to introduce transforms
---
public/pages/Main/Main.tsx | 40 ++++++++++++++++++++++++++++++++++++++
public/utils/constants.ts | 8 ++++++++
2 files changed, 48 insertions(+)
diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx
index f930f2cb9..f92019afc 100644
--- a/public/pages/Main/Main.tsx
+++ b/public/pages/Main/Main.tsx
@@ -39,6 +39,7 @@ enum Navigation {
ManagedIndices = "Managed Indices",
Indices = "Indices",
Rollups = "Rollup Jobs",
+ Transforms = "Transform Jobs",
}
enum Pathname {
@@ -46,6 +47,7 @@ enum Pathname {
ManagedIndices = "/managed-indices",
Indices = "/indices",
Rollups = "/rollups",
+ Transforms = "/transforms",
}
interface MainProps extends RouteComponentProps {}
@@ -85,6 +87,12 @@ export default class Main extends Component {
href: `#${Pathname.Rollups}`,
isSelected: pathname === Pathname.Rollups,
},
+ {
+ name: Navigation.Transforms,
+ id: 5,
+ href: `#${Pathname.Transforms}`,
+ isSelected: pathname === Pathname.Transforms
+ },
],
},
];
@@ -184,6 +192,38 @@ export default class Main extends Component {
)}
/>
+ (
+
+
+
+ )}
+ />
+ (
+
+
+
+ )}
+ />
+ (
+
+
+
+ )}
+ />
+ (
+
+
+
+ )}
+ />
diff --git a/public/utils/constants.ts b/public/utils/constants.ts
index 19368625f..61db333ca 100644
--- a/public/utils/constants.ts
+++ b/public/utils/constants.ts
@@ -30,6 +30,10 @@ export const ROUTES = Object.freeze({
CREATE_ROLLUP: "/create-rollup",
EDIT_ROLLUP: "/edit-rollup",
ROLLUP_DETAILS: "/rollup-details",
+ TRANSFORMS: "/transforms",
+ CREATE_TRANSFORM: "/create-transform",
+ EDIT_TRANSFORM: "/edit-transform",
+ TRANSFORM_DETAILS: "/transform-details",
});
export const BREADCRUMBS = Object.freeze({
@@ -44,6 +48,10 @@ export const BREADCRUMBS = Object.freeze({
CREATE_ROLLUP: { text: "Create rollup job" },
EDIT_ROLLUP: { text: "Edit rollup job" },
ROLLUP_DETAILS: { text: "Rollup details" },
+ TRANSFORMS: { text: "Transform jobs", href: `#${ROUTES.TRANSFORMS}` },
+ CREATE_TRANSFORM: { text: "Create transform job" },
+ EDIT_TRANSFORM: { text: "Edit transform job" },
+ TRANSFORM_DETAILS: { text: "Transform details" },
});
// TODO: Kibana EUI has a SortDirection already
From eac5e4445e117ab656757d55ed9fb746a01b6033 Mon Sep 17 00:00:00 2001
From: Ravi Thaluru
Date: Mon, 19 Apr 2021 22:27:50 -0700
Subject: [PATCH 03/93] Adding transform models and client service
---
models/interfaces.ts | 46 ++++++++++++++++++++
public/services/TransformService.ts | 67 +++++++++++++++++++++++++++++
server/models/interfaces.ts | 37 +++++++++++++++-
utils/constants.ts | 1 +
4 files changed, 150 insertions(+), 1 deletion(-)
create mode 100644 public/services/TransformService.ts
diff --git a/models/interfaces.ts b/models/interfaces.ts
index c8e4860a6..5d122bcb8 100644
--- a/models/interfaces.ts
+++ b/models/interfaces.ts
@@ -67,6 +67,14 @@ export interface DocumentRollup {
metadata: any;
}
+export interface DocumentTransform {
+ _id: string;
+ _seqNo: string;
+ _primaryTerm: number;
+ transform: Transform;
+ metadata: any;
+}
+
// TODO: Fill out when needed
// TODO: separate a frontend Policy from backendPolicy
export interface Policy {
@@ -125,6 +133,44 @@ export interface RollupMetadata {
};
}
+export interface Transform {
+ description: string;
+ groups: RollupDimensionItem[];
+ enabled: boolean;
+ enabled_at: number | null;
+ updated_at: number;
+ metadata_id: string | null;
+ aggregations: Map;
+ page_size: number;
+ schedule: IntervalSchedule | CronSchedule;
+ schema_version: number;
+ source_index: string;
+ target_index: string;
+ roles: String[];
+ data_selection_query: Map;
+}
+
+export interface TransformMetadata {
+ metadata_id: string;
+ transform_metadata: {
+ id: string;
+ seq_no: number;
+ primary_term: number;
+ transform_id: string;
+ after_key: Map | null;
+ last_updated_at: number;
+ status: string;
+ failure_reason: string | null;
+ stats: {
+ pages_processed: number | null;
+ documents_processed: number | null;
+ documents_indexed: number | null;
+ index_time_in_millis: number | null;
+ search_time_in_millis: number | null;
+ }
+ }
+}
+
export interface IntervalSchedule {
interval: {
startTime: number | null;
diff --git a/public/services/TransformService.ts b/public/services/TransformService.ts
new file mode 100644
index 000000000..e7ec6d607
--- /dev/null
+++ b/public/services/TransformService.ts
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import {HttpSetup} from "kibana/public";
+import {ServerResponse} from "../../server/models/types";
+import {GetTransformResponse, PutTransformResponse} from "../../server/models/interfaces";
+import {NODE_API} from "../../utils/constants";
+import {DocumentTransform, Transform} from "../../models/interfaces";
+
+export default class TransformService {
+ httpClient: HttpSetup;
+
+ constructor(httpClient: HttpSetup) {
+ this.httpClient = httpClient
+ }
+
+ getTransforms = async (queryObject: object): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}`
+ // @ts-ignore
+ return (await this.httpClient.get(url, { query: queryObject })) as ServerResponse;
+ };
+
+ putTransform = async (
+ transform: Transform,
+ transformId: string,
+ seqNo?: number,
+ primaryTerm?: number
+ ): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}/${transformId}`
+ return (await this.httpClient.put(url, { query: {seqNo, primaryTerm}, body: JSON.stringify(transform) })) as
+ ServerResponse;
+ };
+
+ getTransform = async (transformId: string): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}/${transformId}`
+ return (await this.httpClient.get(url)) as ServerResponse
+ };
+
+ deleteTransform = async(transformId: string): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}/${transformId}`;
+ return (await this.httpClient.delete(url)) as ServerResponse;
+ };
+
+ startTransform = async(transformId: string): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}/${transformId}/_start`;
+ return (await this.httpClient.post(url)) as ServerResponse;
+ };
+
+ stopTransform = async(transformId: string): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}/${transformId}/_stop`;
+ return (await this.httpClient.post(url)) as ServerResponse;
+ }
+
+ // TODO: implement preview transform
+}
diff --git a/server/models/interfaces.ts b/server/models/interfaces.ts
index 1075be34d..b7e3a497c 100644
--- a/server/models/interfaces.ts
+++ b/server/models/interfaces.ts
@@ -14,7 +14,14 @@
*/
import { IndexService, ManagedIndexService, PolicyService, RollupService } from "../services";
-import { DocumentPolicy, DocumentRollup, ManagedIndexItem, Rollup } from "../../models/interfaces";
+import {
+ DocumentPolicy,
+ DocumentRollup,
+ DocumentTransform,
+ ManagedIndexItem,
+ Rollup,
+ Transform
+} from "../../models/interfaces";
export interface NodeServices {
indexService: IndexService;
@@ -88,6 +95,23 @@ export interface PutRollupResponse {
rollup: { rollup: Rollup };
}
+export interface DeleteTransformResponse {
+ result: string;
+}
+
+export interface GetTransformResponse {
+ transforms: DocumentTransform[];
+ totalTransforms: number;
+ metadata: any;
+}
+
+export interface PutTransformResponse {
+ _id: string;
+ _primary_term: string;
+ _seq_no: string;
+ transform: { transform: Transform };
+}
+
export interface IndexUpdateResponse {
updatedIndices: number;
failures: boolean;
@@ -139,6 +163,17 @@ export interface PutRollupParams {
body: string;
}
+export interface PutTransformParams {
+ transformId: string;
+ if_seq_no?: string;
+ if_primary_term?: string;
+ body: string;
+}
+
+export interface DeleteTransformParams {
+ transformId: string;
+}
+
// TODO: remove optional failedIndices after fixing retry API to always array
export interface RetryResponse {
failures: boolean;
diff --git a/utils/constants.ts b/utils/constants.ts
index f76d056bc..7db04af57 100644
--- a/utils/constants.ts
+++ b/utils/constants.ts
@@ -22,6 +22,7 @@ export const NODE_API = Object.freeze({
EDIT_ROLLOVER_ALIAS: `${BASE_API_PATH}/editRolloverAlias`,
POLICIES: `${BASE_API_PATH}/policies`,
ROLLUPS: `${BASE_API_PATH}/rollups`,
+ TRANSFORMS: `${BASE_API_PATH}/transforms`,
MANAGED_INDICES: `${BASE_API_PATH}/managedIndices`,
RETRY: `${BASE_API_PATH}/retry`,
CHANGE_POLICY: `${BASE_API_PATH}/changePolicy`,
From 96c3d0356ecc4aa7fd17fec5e7d70892a14bf8eb Mon Sep 17 00:00:00 2001
From: Ravi Thaluru
Date: Wed, 21 Apr 2021 14:54:35 -0700
Subject: [PATCH 04/93] Adding changes to get transform page
---
public/models/interfaces.ts | 2 +
public/pages/Main/Main.tsx | 3 +-
.../components/DeleteModal/DeleteModal.tsx | 67 ++++
.../components/DeleteModal/index.ts | 18 +
.../TransformEmptyPrompt.tsx | 71 ++++
.../components/TransformEmptyPrompt/index.ts | 18 +
.../containers/Transforms/Transforms.tsx | 352 ++++++++++++++++++
public/pages/Transforms/models/interfaces.ts | 25 ++
public/pages/Transforms/utils/constants.tsx | 25 ++
public/pages/Transforms/utils/helpers.ts | 32 ++
.../pages/Transforms/utils/metadataHelper.tsx | 85 +++++
public/services/index.ts | 5 +-
12 files changed, 701 insertions(+), 2 deletions(-)
create mode 100644 public/pages/Transforms/components/DeleteModal/DeleteModal.tsx
create mode 100644 public/pages/Transforms/components/DeleteModal/index.ts
create mode 100644 public/pages/Transforms/components/TransformEmptyPrompt/TransformEmptyPrompt.tsx
create mode 100644 public/pages/Transforms/components/TransformEmptyPrompt/index.ts
create mode 100644 public/pages/Transforms/containers/Transforms/Transforms.tsx
create mode 100644 public/pages/Transforms/models/interfaces.ts
create mode 100644 public/pages/Transforms/utils/constants.tsx
create mode 100644 public/pages/Transforms/utils/helpers.ts
create mode 100644 public/pages/Transforms/utils/metadataHelper.tsx
diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts
index 4fbade055..bf614f57c 100644
--- a/public/models/interfaces.ts
+++ b/public/models/interfaces.ts
@@ -14,10 +14,12 @@
*/
import { IndexService, ManagedIndexService, PolicyService, RollupService } from "../services";
+import TransformService from "../services/TransformService";
export interface BrowserServices {
indexService: IndexService;
managedIndexService: ManagedIndexService;
policyService: PolicyService;
rollupService: RollupService;
+ transformService: TransformService;
}
diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx
index f92019afc..1f2f426e3 100644
--- a/public/pages/Main/Main.tsx
+++ b/public/pages/Main/Main.tsx
@@ -32,6 +32,7 @@ import { CoreServicesConsumer } from "../../components/core_services";
import CreateRollupForm from "../CreateRollup/containers/CreateRollupForm";
import EditRollup from "../EditRollup/containers";
import RollupDetails from "../RollupDetails/containers/RollupDetails";
+import Transforms from "../Transforms/containers/Transforms/Transforms";
enum Navigation {
IndexManagement = "Index Management",
@@ -196,7 +197,7 @@ export default class Main extends Component {
path={ROUTES.TRANSFORMS}
render = {(props: RouteComponentProps) => (
-
+
)}
/>
diff --git a/public/pages/Transforms/components/DeleteModal/DeleteModal.tsx b/public/pages/Transforms/components/DeleteModal/DeleteModal.tsx
new file mode 100644
index 000000000..1d6539e15
--- /dev/null
+++ b/public/pages/Transforms/components/DeleteModal/DeleteModal.tsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component, Fragment } from "react";
+import { EuiConfirmModal, EuiForm, EuiFormRow, EuiFieldText, EuiOverlayMask, EuiSpacer } from "@elastic/eui";
+
+// TODO: Merge with Rollup to create generic component
+interface DeleteModalProps {
+ item: string;
+ closeDeleteModal: (event?: any) => void;
+ onClickDelete: (event?: any) => void;
+}
+
+interface DeleteModalState {
+ confirmDeleteText: string;
+}
+
+export default class DeleteModal extends Component {
+ state = { confirmDeleteText: "" };
+
+ onChange = (e: ChangeEvent): void => {
+ this.setState({ confirmDeleteText: e.target.value });
+ };
+
+ render() {
+ const { item, closeDeleteModal, onClickDelete } = this.props;
+ const { confirmDeleteText } = this.state;
+
+ return (
+
+
+
+
+ By deleting "{item}", all future scheduled executions will be canceled. However, your target index
+ and data in it will remain intact.
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/Transforms/components/DeleteModal/index.ts b/public/pages/Transforms/components/DeleteModal/index.ts
new file mode 100644
index 000000000..0d2c98428
--- /dev/null
+++ b/public/pages/Transforms/components/DeleteModal/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import DeleteModal from "./DeleteModal";
+
+export default DeleteModal;
diff --git a/public/pages/Transforms/components/TransformEmptyPrompt/TransformEmptyPrompt.tsx b/public/pages/Transforms/components/TransformEmptyPrompt/TransformEmptyPrompt.tsx
new file mode 100644
index 000000000..9a4868e1d
--- /dev/null
+++ b/public/pages/Transforms/components/TransformEmptyPrompt/TransformEmptyPrompt.tsx
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React from "react";
+import { EuiButton, EuiEmptyPrompt, EuiText } from "@elastic/eui";
+import {PLUGIN_NAME, ROUTES} from "../../../../utils/constants";
+
+interface TransformEmptyPromptProps {
+ filterIsApplied: boolean;
+ loading: boolean;
+ resetFilters: () => void;
+}
+
+export const TEXT = {
+ RESET_FILTERS: "There are no transform jobs matching your applied filters. Reset your filters to view your transform jobs.",
+ NO_TRANSFORMS:
+ "Transform jobs help you create a materialized view on top of existing data.",
+ LOADING: "Loading transform jobs...",
+};
+
+const getMessagePrompt = ({ filterIsApplied, loading }: TransformEmptyPromptProps) => {
+ if (loading) return TEXT.LOADING;
+ if (filterIsApplied) return TEXT.RESET_FILTERS;
+ return TEXT.NO_TRANSFORMS;
+};
+
+const getActions: React.SFC = ({ filterIsApplied, loading, resetFilters }) => {
+ if (loading) {
+ return null;
+ }
+
+ if (filterIsApplied) {
+ return (
+
+ Reset Filters
+
+ );
+ }
+
+ return (
+
+ Create transform
+
+ );
+};
+
+const TransformEmptyPrompt: React.SFC = (props) => (
+
+ {getMessagePrompt(props)}
+
+ }
+ actions={getActions(props)}
+ />
+);
+
+export default TransformEmptyPrompt;
diff --git a/public/pages/Transforms/components/TransformEmptyPrompt/index.ts b/public/pages/Transforms/components/TransformEmptyPrompt/index.ts
new file mode 100644
index 000000000..7a5714db4
--- /dev/null
+++ b/public/pages/Transforms/components/TransformEmptyPrompt/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import TransformEmptyPrompt from "./TransformEmptyPrompt";
+
+export default TransformEmptyPrompt;
diff --git a/public/pages/Transforms/containers/Transforms/Transforms.tsx b/public/pages/Transforms/containers/Transforms/Transforms.tsx
new file mode 100644
index 000000000..044cffc2d
--- /dev/null
+++ b/public/pages/Transforms/containers/Transforms/Transforms.tsx
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import {
+ Direction,
+ EuiPanel,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiTitle,
+ EuiButton,
+ EuiBasicTable,
+ EuiPopover,
+ EuiContextMenuPanel,
+ EuiFieldSearch,
+ EuiHorizontalRule,
+ EuiPagination,
+ EuiLink,
+ EuiTextColor,
+ EuiTableFieldDataColumnType,
+ EuiContextMenuItem,
+ // @ts-ignore
+ Pagination,
+ EuiTableSelectionType,
+ EuiTableSortingType,
+} from "@elastic/eui";
+import queryString from "query-string";
+import { RouteComponentProps } from "react-router-dom";
+import TransformService from "../../../../services/TransformService";
+import {DocumentTransform} from "../../../../../models/interfaces";
+import React, { Component } from "react";
+import { CoreServicesContext } from "../../../../components/core_services";
+import {getURLQueryParams} from "../../utils/helpers";
+import {TransformQueryParams} from "../../models/interfaces";
+import {getErrorMessage} from "../../../../utils/helpers";
+import {ROUTES} from "../../../../utils/constants";
+import DeleteModal from "../../components/DeleteModal";
+import TransformEmptyPrompt from "../../components/TransformEmptyPrompt";
+import {renderEnabled, renderStatus} from "../../utils/metadataHelper";
+import {DEFAULT_PAGE_SIZE_OPTIONS} from "../../../Indices/utils/constants";
+import _ from "lodash";
+
+interface TransformProps extends RouteComponentProps {
+ transformService: TransformService
+}
+
+interface TransformState {
+ totalTransforms: number;
+ from: number;
+ size: number;
+ search: string;
+ sortField: keyof DocumentTransform;
+ sortDirection: Direction;
+ selectedItems: DocumentTransform[];
+ transforms: DocumentTransform[];
+ fetchingTransforms: boolean;
+ transformMetadata: {};
+ isPopOverOpen: boolean;
+ isDeleteModalVisible: boolean;
+}
+
+export default class Transforms extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: TransformProps) {
+ super(props);
+
+ const { from, size, search, sortField, sortDirection } = getURLQueryParams(this.props.location);
+
+ this.state = {
+ totalTransforms: 0,
+ from,
+ size,
+ search,
+ sortField,
+ sortDirection,
+ selectedItems: [],
+ transforms: [],
+ fetchingTransforms: false,
+ transformMetadata: {},
+ isPopOverOpen: false,
+ isDeleteModalVisible: false,
+ };
+
+ this.getTransforms = _.debounce(this.getTransforms, 500, { leading: true });
+ };
+
+ render() {
+ const {
+ totalTransforms,
+ from,
+ size,
+ search,
+ sortField,
+ sortDirection,
+ selectedItems,
+ transforms,
+ fetchingTransforms,
+ isPopOverOpen,
+ isDeleteModalVisible,
+ } = this.state;
+
+ const filterIsApplied = !!search;
+ const pageCount = Math.ceil(totalTransforms / size) || 1;
+ const page = Math.floor(from / size);
+ const pagination: Pagination = {
+ pageIndex: page,
+ pageSize: size,
+ pageSizeOptions: DEFAULT_PAGE_SIZE_OPTIONS,
+ totalItemCount: totalTransforms,
+ };
+
+ const columns: EuiTableFieldDataColumnType[] = [
+ {
+ field: "_id",
+ name: "Name",
+ sortable: true,
+ textOnly: true,
+ truncateText: true,
+ render: (_id) => (
+ this.props.history.push(`${ROUTES.TRANSFORM_DETAILS}?id=${_id}`)} data-test-subj={`transformLink_${_id}`}>
+ {_id}
+
+ ),
+ },
+ {
+ field: "transform.source_index",
+ name: "Source index",
+ sortable: true,
+ textOnly: true,
+ truncateText: true,
+ },
+ {
+ field: "transform.target_index",
+ name: "Target index",
+ sortable: true,
+ textOnly: true,
+ truncateText: true,
+ },
+ {
+ field: "transform.enabled",
+ name: "Job state",
+ sortable: true,
+ textOnly: true,
+ truncateText: true,
+ render: renderEnabled,
+ },
+ {
+ field: "metadata",
+ name: "transform job status",
+ sortable: false,
+ textOnly: true,
+ render: (metadata) => renderStatus(metadata),
+ },
+ ];
+
+ const actionButton = (
+
+ Actions
+
+ );
+
+ const actionItems = [
+ {
+ this.closePopover();
+ this.onClickEdit();
+ }}
+ >
+ Edit
+ ,
+ {
+ this.closePopover();
+ this.showDeleteModal();
+ }}
+ >
+ Delete
+ ,
+ ];
+
+ const selection: EuiTableSelectionType = {
+ onSelectionChange: this.onSelectionChange,
+ };
+
+ const sorting: EuiTableSortingType = {
+ sort: {
+ direction: sortDirection,
+ field: sortField,
+ },
+ };
+
+
+ return (
+
+
+
+
+ {"Transform jobs (" + `${transforms.length}` + ")"}
+
+
+
+
+
+
+ Disable
+
+
+
+ {
+ this.onEnable();
+ }}
+ data-test-subj="enableButton"
+ >
+ Enable
+
+
+
+
+
+
+
+
+
+ Create transform job
+
+
+
+
+
+
+
+
+
+
+
+ {pageCount > 1 && (
+
+
+
+ )}
+
+
+
+
+
+ }
+ onChange={this.onTableChange}
+ pagination={pagination}
+ selection={selection}
+ sorting={sorting}
+ tableLayout="auto"
+ />
+ {isDeleteModalVisible && (
+
+ )}
+
+
+ );
+ }
+
+ getTransforms = async(): Promise => {
+ this.setState( { fetchingTransforms: true });
+ try {
+ const { transformService, history } = this.props;
+ const queryObject = Transforms.getQueryObjectFromState(this.state);
+ const queryParamsString = queryString.stringify(Transforms.getQueryObjectFromState(this.state));
+ history.replace({ ...this.props.location, search: queryParamsString });
+ const response = await transformService.getTransforms(queryObject);
+ if (response.ok) {
+ const { transforms, totalTransforms, metadata } = response.response;
+ this.setState({transforms, totalTransforms, transformMetadata: metadata});
+ } else {
+ this.context.notifications.toasts.addDanger(response.error);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, "There was problem loading transforms"));
+ }
+ this.setState({ fetchingTransforms: false });
+ };
+
+ getSelectedTransformIds() { return "asd" };
+ onSelectionChange() {};
+ closeDeleteModal() {};
+ onClickCreate() {};
+ onClickDelete() {};
+ onPageClick() {};
+ onTableChange() {};
+ onEnable() {};
+ onDisable() {};
+ closePopover() {};
+ resetFilters() {};
+ onSearchChange() {};
+ onActionButtonClick() {};
+ onClickEdit() {};
+ showDeleteModal() {};
+
+
+ static getQueryObjectFromState(transformState : TransformState) : TransformQueryParams {
+ return transformState;
+ }
+}
+
diff --git a/public/pages/Transforms/models/interfaces.ts b/public/pages/Transforms/models/interfaces.ts
new file mode 100644
index 000000000..620afece3
--- /dev/null
+++ b/public/pages/Transforms/models/interfaces.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import { Direction } from "@elastic/eui";
+import {DocumentTransform} from "../../../../models/interfaces";
+
+export interface TransformQueryParams {
+ from: number;
+ size: number;
+ search: string;
+ sortField: keyof DocumentTransform;
+ sortDirection: Direction;
+}
diff --git a/public/pages/Transforms/utils/constants.tsx b/public/pages/Transforms/utils/constants.tsx
new file mode 100644
index 000000000..3d26beeb6
--- /dev/null
+++ b/public/pages/Transforms/utils/constants.tsx
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import {SortDirection} from "../../../utils/constants";
+
+// TODO: Consolidate with Rollup
+export const DEFAULT_QUERY_PARAMS = {
+ from: 0,
+ size: 20,
+ search: "",
+ sortField: "_id",
+ sortDirection: SortDirection.DESC,
+};
diff --git a/public/pages/Transforms/utils/helpers.ts b/public/pages/Transforms/utils/helpers.ts
new file mode 100644
index 000000000..c305ee1ae
--- /dev/null
+++ b/public/pages/Transforms/utils/helpers.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import queryString from "query-string";
+import {TransformQueryParams} from "../models/interfaces";
+import {DEFAULT_QUERY_PARAMS} from "./constants";
+
+export function getURLQueryParams(location: { search: string }): TransformQueryParams {
+ const { from, size, search, sortField, sortDirection } = queryString.parse(location.search);
+
+ return {
+ // @ts-ignores
+ from: isNaN(parseInt(from, 10)) ? DEFAULT_QUERY_PARAMS.from : parseInt(from, 10),
+ // @ts-ignores
+ size: isNaN(parseInt(size, 10)) ? DEFAULT_QUERY_PARAMS.size : parseInt(size, 10),
+ search: typeof search !== "string" ? DEFAULT_QUERY_PARAMS.search : search,
+ sortField: typeof sortField !== "string" ? DEFAULT_QUERY_PARAMS.sortField : sortField,
+ sortDirection: typeof sortDirection !== "string" ? DEFAULT_QUERY_PARAMS.sortDirection : sortDirection,
+ };
+}
diff --git a/public/pages/Transforms/utils/metadataHelper.tsx b/public/pages/Transforms/utils/metadataHelper.tsx
new file mode 100644
index 000000000..c05f4c6b5
--- /dev/null
+++ b/public/pages/Transforms/utils/metadataHelper.tsx
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import {TransformMetadata} from "../../../../models/interfaces";
+import React from "react";
+import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from "@elastic/eui";
+
+// TODO: merge with rollup helper to have a common helper
+export const renderStatus = (metadata: TransformMetadata | undefined): JSX.Element => {
+ if (metadata == null || metadata.transform_metadata == null) return -;
+ let icon;
+ let iconColor;
+ let textColor: "default" | "subdued" | "secondary" | "ghost" | "accent" | "warning" | "danger" | undefined;
+ let text;
+ switch (metadata.transform_metadata.status) {
+ case "failed":
+ icon = "alert";
+ iconColor = "danger";
+ textColor = "danger";
+ text = "Failed: " + metadata.transform_metadata.failure_reason;
+ break;
+ case "finished":
+ icon = "check";
+ iconColor = "success";
+ textColor = "secondary";
+ text = "Complete";
+ break;
+ case "init":
+ return (
+
+
+
+
+
+
+ Initializing
+
+
+
+ );
+ case "started":
+ icon = "play";
+ iconColor = "success";
+ textColor = "secondary";
+ text = "Started";
+ break;
+ case "stopped":
+ icon = "stop";
+ iconColor = "subdued";
+ textColor = "subdued";
+ text = "Stopped";
+ break;
+ default:
+ return -;
+ }
+
+ return (
+
+
+
+
+
+
+ {text}
+
+
+
+ );
+};
+
+export const renderEnabled = (isEnabled: boolean): string => {
+ return isEnabled ? "Enabled" : "Disabled";
+};
diff --git a/public/services/index.ts b/public/services/index.ts
index b1402f8f4..ecd048210 100644
--- a/public/services/index.ts
+++ b/public/services/index.ts
@@ -18,5 +18,8 @@ import IndexService from "./IndexService";
import ManagedIndexService from "./ManagedIndexService";
import PolicyService from "./PolicyService";
import RollupService from "./RollupService";
+import TransformService from "./TransformService";
-export { ServicesConsumer, ServicesContext, IndexService, ManagedIndexService, PolicyService, RollupService };
+export {
+ ServicesConsumer, ServicesContext, IndexService, ManagedIndexService, PolicyService, RollupService, TransformService
+};
From 897c73f41b116df777bf6c8819aebf8a5f58546d Mon Sep 17 00:00:00 2001
From: Eric Lobdell
Date: Tue, 27 Apr 2021 10:55:36 -0700
Subject: [PATCH 05/93] Initial draft commit for CreateTransform UI
---
.../ConfigureTransform/ConfigureTransform.tsx | 56 +++
.../components/ConfigureTransform/index.ts | 18 +
.../CreateTransformSteps.tsx | 51 +++
.../components/CreateTransformSteps/index.ts | 18 +
.../JobNameAndIndices/JobNameAndIndices.tsx | 93 ++++
.../components/JobNameAndIndices/index.ts | 18 +
.../components/Schedule/Schedule.tsx | 95 ++++
.../components/Schedule/index.ts | 18 +
.../TransformIndices/TransformIndices.tsx | 175 ++++++++
.../components/TransformIndices/index.ts | 18 +
.../CreateTransform/CreateTransform.tsx | 73 ++++
.../containers/CreateTransform/index.ts | 18 +
.../CreateTransformForm.tsx | 411 ++++++++++++++++++
.../containers/CreateTransformForm/index.ts | 18 +
.../CreateTransformStep2.tsx | 65 +++
.../containers/CreateTransformStep2/index.ts | 18 +
.../CreateTransformStep3.tsx | 146 +++++++
.../containers/CreateTransformStep3/index.ts | 18 +
.../CreateTransformStep4.tsx | 84 ++++
.../containers/CreateTransformStep4/index.ts | 18 +
public/pages/CreateTransform/index.ts | 18 +
.../pages/CreateTransform/utils/constants.ts | 88 ++++
public/pages/CreateTransform/utils/helpers.ts | 63 +++
public/pages/Main/Main.tsx | 3 +-
.../containers/Transforms/Transforms.tsx | 3 +-
25 files changed, 1601 insertions(+), 3 deletions(-)
create mode 100644 public/pages/CreateTransform/components/ConfigureTransform/ConfigureTransform.tsx
create mode 100644 public/pages/CreateTransform/components/ConfigureTransform/index.ts
create mode 100644 public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
create mode 100644 public/pages/CreateTransform/components/CreateTransformSteps/index.ts
create mode 100644 public/pages/CreateTransform/components/JobNameAndIndices/JobNameAndIndices.tsx
create mode 100644 public/pages/CreateTransform/components/JobNameAndIndices/index.ts
create mode 100644 public/pages/CreateTransform/components/Schedule/Schedule.tsx
create mode 100644 public/pages/CreateTransform/components/Schedule/index.ts
create mode 100644 public/pages/CreateTransform/components/TransformIndices/TransformIndices.tsx
create mode 100644 public/pages/CreateTransform/components/TransformIndices/index.ts
create mode 100644 public/pages/CreateTransform/containers/CreateTransform/CreateTransform.tsx
create mode 100644 public/pages/CreateTransform/containers/CreateTransform/index.ts
create mode 100644 public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
create mode 100644 public/pages/CreateTransform/containers/CreateTransformForm/index.ts
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep2/index.ts
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep3/CreateTransformStep3.tsx
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep3/index.ts
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep4/CreateTransformStep4.tsx
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep4/index.ts
create mode 100644 public/pages/CreateTransform/index.ts
create mode 100644 public/pages/CreateTransform/utils/constants.ts
create mode 100644 public/pages/CreateTransform/utils/helpers.ts
diff --git a/public/pages/CreateTransform/components/ConfigureTransform/ConfigureTransform.tsx b/public/pages/CreateTransform/components/ConfigureTransform/ConfigureTransform.tsx
new file mode 100644
index 000000000..6a1fa6732
--- /dev/null
+++ b/public/pages/CreateTransform/components/ConfigureTransform/ConfigureTransform.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent } from "react";
+import { EuiSpacer, EuiFormRow, EuiFieldText, EuiTextArea, EuiText, EuiFlexGroup, EuiFlexItem } from "@elastic/eui";
+import { ContentPanel } from "../../../../components/ContentPanel";
+
+interface ConfigureTransformProps {
+ isEdit: boolean;
+ transformId: string;
+ transformIdError: string;
+ onChangeName: (value: ChangeEvent) => void;
+ onChangeDescription: (value: ChangeEvent) => void;
+ description: string;
+}
+
+const ConfigureTransform = ({ isEdit, transformId, transformIdError, onChangeName, onChangeDescription, description }: ConfigureTransformProps) => (
+
+
+
+
+
+
+
+
+
+
+ Description
+
+
+
+
+ - optional
+
+
+
+
+
+
+
+
+
+);
+export default ConfigureTransform;
diff --git a/public/pages/CreateTransform/components/ConfigureTransform/index.ts b/public/pages/CreateTransform/components/ConfigureTransform/index.ts
new file mode 100644
index 000000000..b935263fd
--- /dev/null
+++ b/public/pages/CreateTransform/components/ConfigureTransform/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import ConfigureTransform from "./ConfigureTransform";
+
+export default ConfigureTransform;
diff --git a/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx b/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
new file mode 100644
index 000000000..02bdc136b
--- /dev/null
+++ b/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+import React from "react";
+import { EuiSteps } from "@elastic/eui";
+
+interface CreateTransformStepsProps {
+ step: number;
+}
+
+const setOfSteps = (step: number) => {
+ return [
+ {
+ title: "Set up indices",
+ children: null,
+ },
+ {
+ title: "Define aggregations and metrics",
+ children: null,
+ status: step < 2 ? "disabled" : null,
+ },
+ {
+ title: "Specify schedule",
+ children: null,
+ status: step < 3 ? "disabled" : null,
+ },
+ {
+ title: "Review and create",
+ children: null,
+ status: step < 4 ? "disabled" : null,
+ },
+ ];
+};
+const CreateTransformSteps = ({ step }: CreateTransformStepsProps) => (
+
+
+
+);
+
+export default CreateTransformSteps;
diff --git a/public/pages/CreateTransform/components/CreateTransformSteps/index.ts b/public/pages/CreateTransform/components/CreateTransformSteps/index.ts
new file mode 100644
index 000000000..fe481f23a
--- /dev/null
+++ b/public/pages/CreateTransform/components/CreateTransformSteps/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransformSteps from "./CreateTransformSteps";
+
+export default CreateTransformSteps;
diff --git a/public/pages/CreateTransform/components/JobNameAndIndices/JobNameAndIndices.tsx b/public/pages/CreateTransform/components/JobNameAndIndices/JobNameAndIndices.tsx
new file mode 100644
index 000000000..e2e01d3dd
--- /dev/null
+++ b/public/pages/CreateTransform/components/JobNameAndIndices/JobNameAndIndices.tsx
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component } from "react";
+import { EuiFlexGrid, EuiSpacer, EuiFlexItem, EuiText } from "@elastic/eui";
+import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
+import { ModalConsumer } from "../../../../components/Modal";
+import { IndexItem } from "../../../../../models/interfaces";
+
+interface JobNameAndIndicesProps {
+ transformId: string;
+ description: string;
+ sourceIndex: { label: string; value?: IndexItem }[];
+ targetIndex: { label: string; value?: IndexItem }[];
+ onChangeStep: (step: number) => void;
+}
+
+export default class JobNameAndIndices extends Component {
+ constructor(props: JobNameAndIndicesProps) {
+ super(props);
+ }
+
+ render() {
+ const { transformId, description, onChangeStep, sourceIndex, targetIndex } = this.props;
+
+ return (
+
+ {() => (
+ onChangeStep(1),
+ },
+ },
+ ]}
+ />
+ )}
+
+ }
+ bodyStyles={{ padding: "initial" }}
+ title="Job name and indices"
+ titleSize="m"
+ >
+
+
+
+
+
+ Name
+ {transformId}
+
+
+
+
+ Source Index
+ {sourceIndex[0].label}
+
+
+
+
+ Target index
+ {targetIndex[0].label}
+
+
+
+
+ Description
+ {description == "" ? "-" : description}
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/components/JobNameAndIndices/index.ts b/public/pages/CreateTransform/components/JobNameAndIndices/index.ts
new file mode 100644
index 000000000..aa4b17446
--- /dev/null
+++ b/public/pages/CreateTransform/components/JobNameAndIndices/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import JobNameAndIndices from "./JobNameAndIndices";
+
+export default JobNameAndIndices;
diff --git a/public/pages/CreateTransform/components/Schedule/Schedule.tsx b/public/pages/CreateTransform/components/Schedule/Schedule.tsx
new file mode 100644
index 000000000..9a8e45021
--- /dev/null
+++ b/public/pages/CreateTransform/components/Schedule/Schedule.tsx
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component } from "react";
+import moment from "moment-timezone";
+import {
+ EuiSpacer,
+ EuiCheckbox,
+ EuiRadioGroup,
+ EuiFormRow,
+ EuiSelect,
+ EuiFieldNumber,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiTextArea,
+ EuiFormHelpText,
+ EuiText,
+} from "@elastic/eui";
+import { DelayTimeunitOptions, ScheduleIntervalTimeunitOptions } from "../../utils/constants";
+import { ContentPanel } from "../../../../components/ContentPanel";
+
+interface ScheduleProps {
+ isEdit: boolean;
+ transformId: string;
+ transformIdError: string;
+ jobEnabledByDefault: boolean;
+ pageSize: number;
+ onChangeJobEnabledByDefault: () => void;
+ onChangePage: (e: ChangeEvent) => void;
+}
+
+const radios = [
+ {
+ id: "no",
+ label: "No",
+ },
+ {
+ id: "yes",
+ label: "Yes",
+ },
+];
+
+const timezones = moment.tz.names().map((tz) => ({ label: tz, text: tz }));
+
+export default class Schedule extends Component {
+ constructor(props: ScheduleProps) {
+ super(props);
+ }
+
+ render() {
+ const {
+ isEdit,
+ jobEnabledByDefault,
+ pageSize,
+ onChangeJobEnabledByDefault,
+ onChangePage,
+ } = this.props;
+ return (
+
+
+ {!isEdit && (
+
+ )}
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/components/Schedule/index.ts b/public/pages/CreateTransform/components/Schedule/index.ts
new file mode 100644
index 000000000..f18d83cb2
--- /dev/null
+++ b/public/pages/CreateTransform/components/Schedule/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import Schedule from "./Schedule";
+
+export default Schedule;
diff --git a/public/pages/CreateTransform/components/TransformIndices/TransformIndices.tsx b/public/pages/CreateTransform/components/TransformIndices/TransformIndices.tsx
new file mode 100644
index 000000000..2b0abb1f0
--- /dev/null
+++ b/public/pages/CreateTransform/components/TransformIndices/TransformIndices.tsx
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component, Fragment } from "react";
+import { EuiSpacer, EuiFormRow, EuiComboBox, EuiCallOut } from "@elastic/eui";
+import { ContentPanel } from "../../../../components/ContentPanel";
+import { EuiComboBoxOptionOption } from "@elastic/eui/src/components/combo_box/types";
+import { IndexItem } from "../../../../../models/interfaces";
+import IndexService from "../../../../services/IndexService";
+import _ from "lodash";
+import { CoreServicesContext } from "../../../../components/core_services";
+
+interface TransformIndicesProps {
+ indexService: IndexService;
+ sourceIndex: { label: string; value?: IndexItem }[];
+ sourceIndexError: string;
+ targetIndex: { label: string; value?: IndexItem }[];
+ targetIndexError: string;
+ onChangeSourceIndex: (options: EuiComboBoxOptionOption[]) => void;
+ onChangeTargetIndex: (options: EuiComboBoxOptionOption[]) => void;
+ hasAggregation: boolean;
+}
+
+interface TransformIndicesState {
+ isLoading: boolean;
+ indexOptions: { label: string; value?: IndexItem }[];
+ targetIndexOptions: { label: string; value?: IndexItem }[];
+}
+
+export default class TransformIndices extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: TransformIndicesProps) {
+ super(props);
+ this.state = {
+ isLoading: true,
+ indexOptions: [],
+ targetIndexOptions: [],
+ };
+
+ this.onIndexSearchChange = _.debounce(this.onIndexSearchChange, 500, { leading: true });
+ }
+
+ async componentDidMount(): Promise {
+ await this.onIndexSearchChange("");
+ }
+
+ onIndexSearchChange = async (searchValue: string): Promise => {
+ const { indexService } = this.props;
+ this.setState({ isLoading: true, indexOptions: [] });
+ try {
+ const queryObject = { from: 0, size: 10, search: searchValue, sortDirection: "desc", sortField: "index" };
+ const getIndicesResponse = await indexService.getIndices(queryObject);
+ if (getIndicesResponse.ok) {
+ const options = searchValue.trim() ? [{ label: `${searchValue}*` }] : [];
+ const indices = getIndicesResponse.response.indices.map((index: IndexItem) => ({
+ label: index.index,
+ }));
+ this.setState({ indexOptions: options.concat(indices), targetIndexOptions: indices });
+ } else {
+ if (getIndicesResponse.error.startsWith("[index_not_found_exception]")) {
+ this.context.notifications.toasts.addDanger("No index available");
+ } else {
+ this.context.notifications.toasts.addDanger(getIndicesResponse.error);
+ }
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(err.message);
+ }
+
+ this.setState({ isLoading: false });
+ };
+
+ onCreateOption = (searchValue: string, flattenedOptions: { label: string; value?: IndexItem }[]): void => {
+ const { targetIndexOptions } = this.state;
+ const { onChangeTargetIndex } = this.props;
+ const normalizedSearchValue = searchValue.trim();
+
+ if (!normalizedSearchValue) {
+ return;
+ }
+
+ const newOption = {
+ label: searchValue,
+ };
+
+ // Create the option if it doesn't exist.
+ if (flattenedOptions.findIndex((option) => option.label.trim() === normalizedSearchValue) === -1) {
+ targetIndexOptions.concat(newOption);
+ this.setState({ targetIndexOptions: targetIndexOptions });
+ }
+ onChangeTargetIndex([newOption]);
+ };
+
+ render() {
+ const {
+ sourceIndex,
+ sourceIndexError,
+ targetIndex,
+ targetIndexError,
+ onChangeSourceIndex,
+ onChangeTargetIndex,
+ hasAggregation,
+ } = this.props;
+ const { isLoading, indexOptions, targetIndexOptions } = this.state;
+ return (
+
+
+
+
+ You can't change indices after creating a job. Double-check the source and target index names before proceeding.
+
+ {hasAggregation && (
+
+
+
+ Note: changing source index will erase all existing definitions about aggregations and metrics.
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/components/TransformIndices/index.ts b/public/pages/CreateTransform/components/TransformIndices/index.ts
new file mode 100644
index 000000000..ffee9e56f
--- /dev/null
+++ b/public/pages/CreateTransform/components/TransformIndices/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import TransformIndices from "./TransformIndices";
+
+export default TransformIndices;
diff --git a/public/pages/CreateTransform/containers/CreateTransform/CreateTransform.tsx b/public/pages/CreateTransform/containers/CreateTransform/CreateTransform.tsx
new file mode 100644
index 000000000..05b223724
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransform/CreateTransform.tsx
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component } from "react";
+import { EuiSpacer, EuiTitle, EuiFlexGroup, EuiFlexItem, EuiComboBoxOptionOption } from "@elastic/eui";
+import { RouteComponentProps } from "react-router-dom";
+import { TransformService } from "../../../../services";
+import ConfigureTransform from "../../components/ConfigureTransform";
+import TransformIndices from "../../components/TransformIndices";
+import CreateTransformSteps from "../../components/CreateTransformSteps";
+import IndexService from "../../../../services/IndexService";
+import { IndexItem } from "../../../../../models/interfaces";
+
+interface CreateTransformProps extends RouteComponentProps {
+ transformService: TransformService;
+ indexService: IndexService;
+ transformId: string;
+ transformIdError: string;
+ submitError: string;
+ isSubmitting: boolean;
+ hasSubmitted: boolean;
+ description: string;
+ sourceIndex: { label: string; value?: IndexItem }[];
+ sourceIndexError: string;
+ targetIndex: { label: string; value?: IndexItem }[];
+ targetIndexError: string;
+ onChangeName: (e: ChangeEvent) => void;
+ onChangeDescription: (value: ChangeEvent) => void;
+ onChangeSourceIndex: (options: EuiComboBoxOptionOption[]) => void;
+ onChangeTargetIndex: (options: EuiComboBoxOptionOption[]) => void;
+ currentStep: number;
+ hasAggregation: boolean;
+}
+
+export default class CreateTransform extends Component {
+ render() {
+ if (this.props.currentStep !== 1) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+ Set up indices
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/containers/CreateTransform/index.ts b/public/pages/CreateTransform/containers/CreateTransform/index.ts
new file mode 100644
index 000000000..b01f74a8a
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransform/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransform from "./CreateTransform";
+
+export default CreateTransform;
diff --git a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
new file mode 100644
index 000000000..16c135c2c
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
@@ -0,0 +1,411 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component } from "react";
+import { EuiButton, EuiButtonEmpty, EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem } from "@elastic/eui";
+import { RouteComponentProps } from "react-router-dom";
+import moment from "moment";
+import { TransformService } from "../../../../services";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import IndexService from "../../../../services/IndexService";
+import { ManagedCatIndex } from "../../../../../server/models/interfaces";
+import CreateTransform from "../CreateTransform";
+import CreateTransformStep2 from "../CreateTransformStep2";
+import { DimensionItem, FieldItem, IndexItem, MetricItem, Transform } from "../../../../../models/interfaces";
+import { getErrorMessage } from "../../../../utils/helpers";
+import { EMPTY_TRANSFORM } from "../../utils/constants";
+import CreateTransformStep3 from "../CreateTransformStep3";
+import CreateTransformStep4 from "../CreateTransformStep4";
+import { compareFieldItem, parseFieldOptions } from "../../utils/helpers";
+import { CoreServicesContext } from "../../../../components/core_services";
+
+interface CreateTransformFormProps extends RouteComponentProps {
+ transformService: TransformService;
+ indexService: IndexService;
+}
+
+interface CreateTransformFormState {
+ currentStep: number;
+ transformId: string;
+ transformIdError: string;
+ transformSeqNo: number | null;
+ transformPrimaryTerm: number | null;
+ submitError: string;
+ isSubmitting: boolean;
+ hasSubmitted: boolean;
+ loadingIndices: boolean;
+ indices: ManagedCatIndex[];
+ totalIndices: number;
+
+ description: string;
+ sourceIndex: { label: string; value?: IndexItem }[];
+ sourceIndexError: string;
+ targetIndex: { label: string; value?: IndexItem }[];
+ targetIndexError: string;
+
+ mappings: any;
+ allMappings: FieldItem[][];
+ fields: FieldItem[];
+ selectedTerms: FieldItem[];
+ selectedDimensionField: DimensionItem[];
+ selectedMetrics: MetricItem[];
+ metricError: string;
+ timestamp: EuiComboBoxOptionOption[];
+ timestampError: string;
+ intervalType: string;
+ intervalValue: number;
+ timezone: string;
+ timeunit: string;
+ selectedFields: FieldItem[];
+ jobEnabledByDefault: boolean;
+
+ continuousJob: string;
+ continuousDefinition: string;
+ interval: number;
+ intervalError: string;
+ intervalTimeunit: string;
+ cronExpression: string;
+ cronTimezone: string;
+ pageSize: number;
+ delayTime: number | undefined;
+ delayTimeunit: string;
+ transformJSON: any;
+}
+
+export default class CreateTransformForm extends Component {
+ static contextType = CoreServicesContext;
+
+ constructor(props: CreateTransformFormProps) {
+ super(props);
+
+ this.state = {
+ currentStep: 1,
+ transformSeqNo: null,
+ transformPrimaryTerm: null,
+ transformId: "",
+ transformIdError: "",
+ submitError: "",
+ isSubmitting: false,
+ hasSubmitted: false,
+ loadingIndices: true,
+ indices: [],
+ totalIndices: 0,
+
+ mappings: "",
+ allMappings: [],
+ fields: [],
+ description: "",
+
+ sourceIndex: [],
+ sourceIndexError: "",
+ targetIndex: [],
+ targetIndexError: "",
+
+ jobEnabledByDefault: true,
+ pageSize: 1000,
+ transformJSON: JSON.parse(EMPTY_TRANSFORM),
+ };
+ this._next = this._next.bind(this);
+ this._prev = this._prev.bind(this);
+ }
+
+ componentDidMount = async (): Promise => {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS, BREADCRUMBS.CREATE_TRANSFORM]);
+ };
+
+ getMappings = async (srcIndex: string): Promise => {
+ if (!srcIndex.length) return;
+ try {
+ const { transformService } = this.props;
+ const response = await transformService.getMappings(srcIndex);
+ if (response.ok) {
+ let allMappings: FieldItem[][] = [];
+ const mappings = response.response;
+ //Push mappings array to allMappings 2D array first
+ for (let index in mappings) {
+ allMappings.push(parseFieldOptions("", mappings[index].mappings.properties));
+ }
+ //Find intersect from all mappings
+ const fields = allMappings.reduce((mappingA, mappingB) =>
+ mappingA.filter((itemA) => mappingB.some((itemB) => compareFieldItem(itemA, itemB)))
+ );
+ this.setState({ mappings, fields, allMappings });
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not load fields: ${response.error}`);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, "Could not load fields"));
+ }
+ };
+
+ _next() {
+ let currentStep = this.state.currentStep;
+ let error = false;
+ //Verification here
+ if (currentStep == 1) {
+ const { transformId, sourceIndex, targetIndex } = this.state;
+
+ if (!transformId) {
+ this.setState({ submitError: "Job name is required.", transformIdError: "Job name is required." });
+ error = true;
+ }
+ if (sourceIndex.length == 0) {
+ this.setState({ submitError: "Source index is required.", sourceIndexError: "Source index is required." });
+ error = true;
+ }
+ if (targetIndex.length == 0) {
+ this.setState({ submitError: "Target index is required.", targetIndexError: "Target index is required." });
+ error = true;
+ }
+ } else if (currentStep == 2) {
+ } else if (currentStep == 3) {
+ //Check if interval is a valid value and is specified.
+ const { intervalError, continuousDefinition } = this.state;
+ if (continuousDefinition == "fixed") {
+ if (intervalError != "") {
+ const intervalErrorMsg = "Interval value is required.";
+ this.setState({ submitError: intervalErrorMsg, intervalError: intervalErrorMsg });
+ error = true;
+ }
+ }
+ }
+
+ if (error) return;
+
+ currentStep = currentStep >= 3 ? 4 : currentStep + 1;
+
+ this.setState({
+ submitError: "",
+ currentStep: currentStep,
+ });
+ }
+
+ _prev() {
+ let currentStep = this.state.currentStep ;
+ // If the current step is 2 or 3, then subtract one on "previous" button click
+ currentStep = currentStep <= 1 ? 1 : currentStep - 1;
+ this.setState({
+ currentStep: currentStep,
+ });
+ }
+
+ onChangeStep = (step: number): void => {
+ if (step > 3) return;
+ this.setState({
+ currentStep: step,
+ });
+ };
+
+ onChangeDescription = (e: ChangeEvent): void => {
+ const description = e.target.value;
+ let newJSON = this.state.transformJSON;
+ newJSON.transform.description = description;
+ this.setState({ description: description, transformJSON: newJSON });
+ };
+
+ onChangeName = (e: ChangeEvent): void => {
+ const transformId = e.target.value;
+ this.setState({ transformId, transformIdError: transformId ? "" : "Name is required" });
+ };
+
+ onChangeSourceIndex = async (options: EuiComboBoxOptionOption[]): Promise => {
+ let newJSON = this.state.transformJSON;
+ let sourceIndex = options.map(function (option) {
+ return option.label;
+ });
+ const sourceIndexError = sourceIndex.length ? "" : "Source index is required";
+ const srcIndexText = sourceIndex.length ? sourceIndex[0] : "";
+ newJSON.transform.source_index = srcIndexText;
+ this.setState({ sourceIndex: options, transformJSON: newJSON, sourceIndexError: sourceIndexError });
+ this.setState({
+ selectedDimensionField: [],
+ selectedMetrics: [],
+ });
+ await this.getMappings(srcIndexText);
+ };
+
+ onChangeTargetIndex = (options: EuiComboBoxOptionOption[]): void => {
+ //Try to get label text from option from the only array element in options, if exists
+ let newJSON = this.state.transformJSON;
+ let targetIndex = options.map(function (option) {
+ return option.label;
+ });
+
+ const targetIndexError = targetIndex.length ? "" : "Target index is required";
+
+ newJSON.transform.target_index = targetIndex[0];
+ this.setState({ targetIndex: options, transformJSON: newJSON, targetIndexError: targetIndexError });
+ };
+
+ setDateHistogram = (): void => {
+ const { intervalType, intervalValue, timeunit } = this.state;
+ let newJSON = this.state.transformJSON;
+ if (intervalType == "calendar") {
+ newJSON.transform.dimensions[0].date_histogram.calendar_interval = `1${timeunit}`;
+ delete newJSON.transform.dimensions[0].date_histogram["fixed_interval"];
+ } else {
+ newJSON.transform.dimensions[0].date_histogram.fixed_interval = `${intervalValue}${timeunit}`;
+ delete newJSON.transform.dimensions[0].date_histogram["calendar_interval"];
+ }
+ this.setState({ transformJSON: newJSON });
+ };
+
+ onChangeJobEnabledByDefault = (): void => {
+ const checked = this.state.jobEnabledByDefault;
+ let newJSON = this.state.transformJSON;
+ newJSON.transform.enabled = !checked;
+ this.setState({ jobEnabledByDefault: !checked, transformJSON: newJSON });
+ };
+
+ onChangePage = (e: ChangeEvent): void => {
+ let newJSON = this.state.transformJSON;
+ newJSON.transform.page_size = e.target.valueAsNumber;
+ this.setState({ pageSize: e.target.valueAsNumber, transformJSON: newJSON });
+ };
+
+ onSubmit = async (): Promise => {
+ const { transformId, transformJSON } = this.state;
+ this.setState({ submitError: "", isSubmitting: true, hasSubmitted: true });
+ try {
+ if (!transformId) {
+ this.setState({ transformIdError: "Required" });
+ } else {
+ this.setDateHistogram();
+ await this.onCreate(transformId, transformJSON);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger("Invalid Transform JSON");
+ console.error(err);
+ }
+
+ this.setState({ isSubmitting: false });
+ };
+
+ onCancel = (): void => {
+ this.props.history.push(ROUTES.TRANSFORMS);
+ };
+
+ onCreate = async (transformId: string, transform: Transform): Promise => {
+ const { transformService } = this.props;
+ try {
+ const response = await transformService.putTransform(transform, transformId);
+ if (response.ok) {
+ this.context.notifications.toasts.addSuccess(`Created transform: ${response.response._id}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ } else {
+ this.setState({ submitError: response.error });
+ this.context.notifications.toasts.addDanger(`Failed to create transform: ${response.error}`);
+ }
+ } catch (err) {
+ this.setState({ submitError: getErrorMessage(err, "There was a problem creating the transform job") });
+ this.context.notifications.toasts.addDanger(
+ `Failed to create transform: ${getErrorMessage(err, "There was a problem creating the transform job")}`
+ );
+ }
+ };
+
+ render() {
+ const {
+ transformId,
+ transformIdError,
+ submitError,
+ isSubmitting,
+ hasSubmitted,
+ description,
+ sourceIndex,
+ sourceIndexError,
+ targetIndex,
+ targetIndexError,
+ currentStep,
+
+ jobEnabledByDefault,
+ pageSize,
+ } = this.state;
+ return (
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/containers/CreateTransformForm/index.ts b/public/pages/CreateTransform/containers/CreateTransformForm/index.ts
new file mode 100644
index 000000000..d933dd68a
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformForm/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransformForm from "./CreateTransformForm";
+
+export default CreateTransformForm;
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
new file mode 100644
index 000000000..9ad07d434
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component } from "react";
+import { EuiSpacer, EuiTitle, EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiComboBoxOptionOption } from "@elastic/eui";
+import { RouteComponentProps } from "react-router-dom";
+import { TransformService } from "../../../../services";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import CreateTransformSteps from "../../components/CreateTransformSteps";
+import { DimensionItem, FieldItem, MetricItem } from "../../../../../models/interfaces";
+import { CoreServicesContext } from "../../../../components/core_services";
+
+interface CreateTransformStep2Props extends RouteComponentProps {
+ transformService: TransformService;
+ currentStep: number;
+}
+
+export default class CreateTransformStep2 extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: CreateTransformStep2Props) {
+ super(props);
+ }
+
+ componentDidMount = async (): Promise => {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS]);
+ };
+
+ onCancel = (): void => {
+ this.props.history.push(ROUTES.TRANSFORMS);
+ };
+
+ render() {
+ if (this.props.currentStep !== 2) return null;
+ const { fields, timestamp } = this.props;
+
+ return (
+
+
+
+
+
+
+
+ Definition Placeholder
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep2/index.ts b/public/pages/CreateTransform/containers/CreateTransformStep2/index.ts
new file mode 100644
index 000000000..1b7f6dbd7
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep2/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransformStep2 from "./CreateTransformStep2";
+
+export default CreateTransformStep2;
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep3/CreateTransformStep3.tsx b/public/pages/CreateTransform/containers/CreateTransformStep3/CreateTransformStep3.tsx
new file mode 100644
index 000000000..c359130ef
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep3/CreateTransformStep3.tsx
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component } from "react";
+import { EuiSpacer, EuiTitle, EuiFlexGroup, EuiFlexItem } from "@elastic/eui";
+import { RouteComponentProps } from "react-router-dom";
+import { TransformService } from "../../../../services";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import { getErrorMessage } from "../../../../utils/helpers";
+import { Transform } from "../../../../../models/interfaces";
+import CreateTransformSteps from "../../components/CreateTransformSteps";
+import Schedule from "../../components/Schedule";
+import { CoreServicesContext } from "../../../../components/core_services";
+
+interface CreateTransformProps extends RouteComponentProps {
+ transformService: TransformService;
+ currentStep: number;
+ jobEnabledByDefault: boolean;
+ pageSize: number;
+ onChangeJobEnabledByDefault: () => void;
+ onChangePage: (e: ChangeEvent) => void;
+}
+
+interface CreateTransformState {
+ transformId: string;
+ transformIdError: string;
+ transformSeqNo: number | null;
+ transformPrimaryTerm: number | null;
+ submitError: string;
+ isSubmitting: boolean;
+ hasSubmitted: boolean;
+}
+
+export default class CreateTransformStep3 extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: CreateTransformProps) {
+ super(props);
+
+ this.state = {
+ transformSeqNo: null,
+ transformPrimaryTerm: null,
+ transformId: "",
+ transformIdError: "",
+ submitError: "",
+ isSubmitting: false,
+ hasSubmitted: false,
+ };
+ }
+
+ componentDidMount = async (): Promise => {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS]);
+ };
+
+ onCreate = async (transformId: string, transform: Transform): Promise => {
+ const { transformService } = this.props;
+ try {
+ const response = await transformService.putTransform(transform, transformId);
+ if (response.ok) {
+ this.context.notifications.toasts.addSuccess(`Created transform: ${response.response._id}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ } else {
+ this.setState({ submitError: response.error });
+ }
+ } catch (err) {
+ this.setState({ submitError: getErrorMessage(err, "There was a problem creating the transform") });
+ }
+ };
+
+ onUpdate = async (transformId: string, transform: Transform): Promise => {
+ try {
+ const { transformService } = this.props;
+ const { transformPrimaryTerm, transformSeqNo } = this.state;
+ if (transformSeqNo == null || transformPrimaryTerm == null) {
+ this.context.notifications.toasts.addDanger("Could not update transform without seqNo and primaryTerm");
+ return;
+ }
+ const response = await transformService.putTransform(transform, transformId, transformSeqNo, transformPrimaryTerm);
+ if (response.ok) {
+ this.context.notifications.toasts.addSuccess(`Updated transform: ${response.response._id}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ } else {
+ this.setState({ submitError: response.error });
+ }
+ } catch (err) {
+ this.setState({ submitError: getErrorMessage(err, "There was a problem updating the transform") });
+ }
+ };
+
+ onCancel = (): void => {
+ this.props.history.push(ROUTES.TRANSFORMS);
+ };
+
+ onChange = (e: ChangeEvent): void => {
+ const { hasSubmitted } = this.state;
+ const transformId = e.target.value;
+ if (hasSubmitted) this.setState({ transformId, transformIdError: transformId ? "" : "Required" });
+ else this.setState({ transformId });
+ };
+
+ render() {
+ if (this.props.currentStep != 3) return null;
+ const {
+ jobEnabledByDefault,
+ pageSize,
+ onChangePage,
+ } = this.props;
+ const { transformId, transformIdError } = this.state;
+ return (
+
+
+
+
+
+
+
+ Specify schedule
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep3/index.ts b/public/pages/CreateTransform/containers/CreateTransformStep3/index.ts
new file mode 100644
index 000000000..c2626fe72
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep3/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransformStep3 from "./CreateTransformStep3";
+
+export default CreateTransformStep3;
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep4/CreateTransformStep4.tsx b/public/pages/CreateTransform/containers/CreateTransformStep4/CreateTransformStep4.tsx
new file mode 100644
index 000000000..328533609
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep4/CreateTransformStep4.tsx
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component } from "react";
+import { EuiSpacer, EuiTitle, EuiFlexGroup, EuiFlexItem, EuiComboBoxOptionOption, EuiCallOut } from "@elastic/eui";
+import { RouteComponentProps } from "react-router-dom";
+import { TransformService } from "../../../../services";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import { DimensionItem, IndexItem, MetricItem } from "../../../../../models/interfaces";
+import CreateTransformSteps from "../../components/CreateTransformSteps";
+import JobNameAndIndices from "../../components/JobNameAndIndices";
+import { CoreServicesContext } from "../../../../components/core_services";
+
+interface CreateTransformProps extends RouteComponentProps {
+ transformService: TransformService;
+ submitError: string;
+ currentStep: number;
+ onChangeStep: (step: number) => void;
+ transformId: string;
+ description: string;
+ sourceIndex: { label: string; value?: IndexItem }[];
+ targetIndex: { label: string; value?: IndexItem }[];
+
+ timestamp: EuiComboBoxOptionOption[];
+ timezone: string;
+ timeunit: string;
+
+ jobEnabledByDefault: boolean;
+ pageSize: number;
+}
+
+export default class CreateTransformStep4 extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: CreateTransformProps) {
+ super(props);
+ }
+
+ componentDidMount = async (): Promise => {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS]);
+ };
+
+ onCancel = (): void => {
+ this.props.history.push(ROUTES.TRANSFORMS);
+ };
+
+ render() {
+ if (this.props.currentStep != 4) return null;
+
+ return (
+
+
+
+
+
+
+
+ Review and create
+
+
+
+
+
+
+ You can't change aggregations or metrics after creating a job. Double-check your choices before proceeding.
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep4/index.ts b/public/pages/CreateTransform/containers/CreateTransformStep4/index.ts
new file mode 100644
index 000000000..379985a8a
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep4/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransformStep4 from "./CreateTransformStep4";
+
+export default CreateTransformStep4;
diff --git a/public/pages/CreateTransform/index.ts b/public/pages/CreateTransform/index.ts
new file mode 100644
index 000000000..2ca67cce6
--- /dev/null
+++ b/public/pages/CreateTransform/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransform from "./containers/CreateTransform";
+
+export default CreateTransform;
diff --git a/public/pages/CreateTransform/utils/constants.ts b/public/pages/CreateTransform/utils/constants.ts
new file mode 100644
index 000000000..008f1bf34
--- /dev/null
+++ b/public/pages/CreateTransform/utils/constants.ts
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+export const EMPTY_TRANSFORM = JSON.stringify({
+ transform: {
+ continuous: false,
+ description: "",
+ dimensions: [
+ {
+ date_histogram: {
+ source_field: "",
+ fixed_interval: "1h",
+ timezone: "UTC",
+ },
+ },
+ ],
+ enabled: true,
+ metrics: [],
+ page_size: 1000,
+ roles: [],
+ schedule: {
+ interval: {
+ start_time: 234802,
+ period: 1,
+ unit: "MINUTES",
+ },
+ },
+ source_index: "",
+ target_index: "",
+ },
+});
+
+export const FixedTimeunitOptions = [
+ { value: "ms", text: "Millisecond(s)" },
+ { value: "s", text: "Second(s)" },
+ { value: "m", text: "Minute(s)" },
+ { value: "h", text: "Hour(s)" },
+ { value: "d", text: "Day(s)" },
+];
+
+export const DelayTimeunitOptions = [
+ { value: "SECONDS", text: "Second(s)" },
+ { value: "MINUTES", text: "Minute(s)" },
+ { value: "HOURS", text: "Hour(s)" },
+ { value: "DAYS", text: "Day(s)" },
+];
+
+export const CalendarTimeunitOptions = [
+ { value: "m", text: "Minute" },
+ { value: "h", text: "Hour" },
+ { value: "d", text: "Day" },
+ { value: "w", text: "Week" },
+ { value: "M", text: "Month" },
+ { value: "q", text: "Quarter" },
+ { value: "y", text: "Year" },
+];
+
+export const ScheduleIntervalTimeunitOptions = [
+ { value: "MINUTES", text: "Minute(s)" },
+ { value: "HOURS", text: "Hour(s)" },
+ { value: "DAYS", text: "Day(s)" },
+];
+
+export const AddFieldsColumns = [
+ {
+ field: "label",
+ name: "Field name",
+ sortable: true,
+ },
+ {
+ field: "type",
+ name: "Field type",
+ sortable: true,
+ render: (type: string | undefined) => (type == null || type == undefined ? "-" : type),
+ },
+];
diff --git a/public/pages/CreateTransform/utils/helpers.ts b/public/pages/CreateTransform/utils/helpers.ts
new file mode 100644
index 000000000..8e66b67fc
--- /dev/null
+++ b/public/pages/CreateTransform/utils/helpers.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import { FieldItem } from "../../../../models/interfaces";
+
+export const parseTimeunit = (timeunit: string): string => {
+ if (timeunit == "ms" || timeunit == "Milliseconds") return "millisecond(s)";
+ else if (timeunit == "SECONDS" || timeunit == "s" || timeunit == "Seconds") return "second(s)";
+ else if (timeunit == "MINUTES" || timeunit == "m" || timeunit == "Minutes") return "minute(s)";
+ else if (timeunit == "HOURS" || timeunit == "h" || timeunit == "Hours") return "hour(s)";
+ else if (timeunit == "DAYS" || timeunit == "d" || timeunit == "Days") return "day(s)";
+ else if (timeunit == "w") return "week";
+ else if (timeunit == "M") return "month";
+ else if (timeunit == "q") return "quarter";
+ else if (timeunit == "y") return "year";
+
+ return timeunit;
+};
+
+//Returns true if field type is numeric
+export const isNumericMapping = (fieldType: string | undefined): boolean => {
+ return (
+ fieldType == "long" ||
+ fieldType == "integer" ||
+ fieldType == "short" ||
+ fieldType == "byte" ||
+ fieldType == "double" ||
+ fieldType == "float" ||
+ fieldType == "half_float" ||
+ fieldType == "scaled_float"
+ );
+};
+
+export const compareFieldItem = (itemA: FieldItem, itemB: FieldItem): boolean => {
+ return itemB.label == itemA.label && itemA.type == itemB.type;
+};
+
+export const parseFieldOptions = (prefix: string, mappings: any): FieldItem[] => {
+ let fieldsOption: FieldItem[] = [];
+ for (let field in mappings) {
+ if (mappings.hasOwnProperty(field)) {
+ if (mappings[field].type != "object" && mappings[field].type != null && mappings[field].type != "nested")
+ fieldsOption.push({ label: prefix + field, type: mappings[field].type });
+ if (mappings[field].fields != null)
+ fieldsOption = fieldsOption.concat(parseFieldOptions(prefix + field + ".", mappings[field].fields));
+ if (mappings[field].properties != null)
+ fieldsOption = fieldsOption.concat(parseFieldOptions(prefix + field + ".", mappings[field].properties));
+ }
+ }
+ return fieldsOption;
+};
diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx
index 1f2f426e3..4f389e157 100644
--- a/public/pages/Main/Main.tsx
+++ b/public/pages/Main/Main.tsx
@@ -30,6 +30,7 @@ import { BrowserServices } from "../../models/interfaces";
import { ROUTES } from "../../utils/constants";
import { CoreServicesConsumer } from "../../components/core_services";
import CreateRollupForm from "../CreateRollup/containers/CreateRollupForm";
+import CreateTransformForm from "../CreateTransform/containers/CreateTransformForm";
import EditRollup from "../EditRollup/containers";
import RollupDetails from "../RollupDetails/containers/RollupDetails";
import Transforms from "../Transforms/containers/Transforms/Transforms";
@@ -205,7 +206,7 @@ export default class Main extends Component {
path={ROUTES.CREATE_TRANSFORM}
render={(props: RouteComponentProps) => (
-
+
)}
/>
diff --git a/public/pages/Transforms/containers/Transforms/Transforms.tsx b/public/pages/Transforms/containers/Transforms/Transforms.tsx
index 044cffc2d..211746a62 100644
--- a/public/pages/Transforms/containers/Transforms/Transforms.tsx
+++ b/public/pages/Transforms/containers/Transforms/Transforms.tsx
@@ -44,7 +44,7 @@ import { CoreServicesContext } from "../../../../components/core_services";
import {getURLQueryParams} from "../../utils/helpers";
import {TransformQueryParams} from "../../models/interfaces";
import {getErrorMessage} from "../../../../utils/helpers";
-import {ROUTES} from "../../../../utils/constants";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
import DeleteModal from "../../components/DeleteModal";
import TransformEmptyPrompt from "../../components/TransformEmptyPrompt";
import {renderEnabled, renderStatus} from "../../utils/metadataHelper";
@@ -349,4 +349,3 @@ export default class Transforms extends Component
Date: Tue, 27 Apr 2021 12:54:50 -0700
Subject: [PATCH 06/93] adding transform service routes and backend connection
---
models/interfaces.ts | 2 +-
public/index_management_app.tsx | 5 +-
public/models/interfaces.ts | 3 +-
.../containers/Transforms/Transforms.tsx | 7 +-
public/services/TransformService.ts | 2 +-
server/clusters/ism/ismPlugin.ts | 22 +++++
server/models/interfaces.ts | 4 +-
server/plugin.ts | 8 +-
server/routes/index.ts | 3 +-
server/routes/transforms.ts | 39 ++++++++
server/services/TransformService.ts | 89 +++++++++++++++++++
server/services/index.ts | 3 +-
server/utils/constants.ts | 2 +
13 files changed, 176 insertions(+), 13 deletions(-)
create mode 100644 server/routes/transforms.ts
create mode 100644 server/services/TransformService.ts
diff --git a/models/interfaces.ts b/models/interfaces.ts
index 5d122bcb8..aff6065b9 100644
--- a/models/interfaces.ts
+++ b/models/interfaces.ts
@@ -69,7 +69,7 @@ export interface DocumentRollup {
export interface DocumentTransform {
_id: string;
- _seqNo: string;
+ _seqNo: number;
_primaryTerm: number;
transform: Transform;
metadata: any;
diff --git a/public/index_management_app.tsx b/public/index_management_app.tsx
index ac4d71bce..d60dc5ffa 100644
--- a/public/index_management_app.tsx
+++ b/public/index_management_app.tsx
@@ -18,7 +18,7 @@ import React from "react";
import ReactDOM from "react-dom";
import { render, unmountComponentAtNode } from "react-dom";
import { HashRouter as Router, Route } from "react-router-dom";
-import { IndexService, ManagedIndexService, PolicyService, RollupService, ServicesContext } from "./services";
+import { IndexService, ManagedIndexService, PolicyService, RollupService, TransformService, ServicesContext } from "./services";
import { DarkModeContext } from "./components/DarkMode";
import Main from "./pages/Main";
import { CoreServicesContext } from "./components/core_services";
@@ -30,7 +30,8 @@ export function renderApp(coreStart: CoreStart, params: AppMountParameters) {
const managedIndexService = new ManagedIndexService(http);
const policyService = new PolicyService(http);
const rollupService = new RollupService(http);
- const services = { indexService, managedIndexService, policyService, rollupService };
+ const transformService = new TransformService(http);
+ const services = { indexService, managedIndexService, policyService, rollupService, transformService };
const isDarkMode = coreStart.uiSettings.get("theme:darkMode") || false;
diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts
index bf614f57c..83d84d071 100644
--- a/public/models/interfaces.ts
+++ b/public/models/interfaces.ts
@@ -13,8 +13,7 @@
* permissions and limitations under the License.
*/
-import { IndexService, ManagedIndexService, PolicyService, RollupService } from "../services";
-import TransformService from "../services/TransformService";
+import { IndexService, ManagedIndexService, PolicyService, RollupService, TransformService } from "../services";
export interface BrowserServices {
indexService: IndexService;
diff --git a/public/pages/Transforms/containers/Transforms/Transforms.tsx b/public/pages/Transforms/containers/Transforms/Transforms.tsx
index 044cffc2d..3a291798e 100644
--- a/public/pages/Transforms/containers/Transforms/Transforms.tsx
+++ b/public/pages/Transforms/containers/Transforms/Transforms.tsx
@@ -44,7 +44,7 @@ import { CoreServicesContext } from "../../../../components/core_services";
import {getURLQueryParams} from "../../utils/helpers";
import {TransformQueryParams} from "../../models/interfaces";
import {getErrorMessage} from "../../../../utils/helpers";
-import {ROUTES} from "../../../../utils/constants";
+import {BREADCRUMBS, ROUTES} from "../../../../utils/constants";
import DeleteModal from "../../components/DeleteModal";
import TransformEmptyPrompt from "../../components/TransformEmptyPrompt";
import {renderEnabled, renderStatus} from "../../utils/metadataHelper";
@@ -95,6 +95,11 @@ export default class Transforms extends Component;
}
-
+
// TODO: implement preview transform
}
diff --git a/server/clusters/ism/ismPlugin.ts b/server/clusters/ism/ismPlugin.ts
index e9e1acc96..8bcd7101c 100644
--- a/server/clusters/ism/ismPlugin.ts
+++ b/server/clusters/ism/ismPlugin.ts
@@ -270,4 +270,26 @@ export default function ismPlugin(Client: any, config: any, components: any) {
},
method: "GET",
});
+
+ // TODO: Add other transform APIs
+
+ ism.getTransform = ca({
+ url: {
+ fmt: `${API.TRANSFORM_BASE}/<%=transformId%>`,
+ req: {
+ transformId: {
+ type: "string",
+ required: true,
+ }
+ }
+ },
+ method: "GET",
+ });
+
+ ism.getTransforms = ca({
+ url: {
+ fmt: `${API.TRANSFORM_BASE}/`,
+ },
+ method: "GET",
+ });
}
diff --git a/server/models/interfaces.ts b/server/models/interfaces.ts
index b7e3a497c..636e9e2de 100644
--- a/server/models/interfaces.ts
+++ b/server/models/interfaces.ts
@@ -13,7 +13,7 @@
* permissions and limitations under the License.
*/
-import { IndexService, ManagedIndexService, PolicyService, RollupService } from "../services";
+import { IndexService, ManagedIndexService, PolicyService, RollupService, TransformService } from "../services";
import {
DocumentPolicy,
DocumentRollup,
@@ -28,6 +28,7 @@ export interface NodeServices {
managedIndexService: ManagedIndexService;
policyService: PolicyService;
rollupService: RollupService;
+ transformService: TransformService;
}
export interface SearchResponse {
@@ -236,6 +237,7 @@ export interface IndexManagementApi {
readonly REMOVE_POLICY_BASE: string;
readonly CHANGE_POLICY_BASE: string;
readonly ROLLUP_JOBS_BASE: string;
+ readonly TRANSFORM_BASE: string;
}
export interface DefaultHeaders {
diff --git a/server/plugin.ts b/server/plugin.ts
index b29558d83..fa94579a1 100644
--- a/server/plugin.ts
+++ b/server/plugin.ts
@@ -16,8 +16,8 @@
import { IndexManagementPluginSetup, IndexManagementPluginStart } from ".";
import { Plugin, CoreSetup, CoreStart, IClusterClient } from "../../../src/core/server";
import ismPlugin from "./clusters/ism/ismPlugin";
-import { PolicyService, ManagedIndexService, IndexService, RollupService } from "./services";
-import { indices, policies, managedIndices, rollups } from "../server/routes";
+import { PolicyService, ManagedIndexService, IndexService, RollupService, TransformService } from "./services";
+import { indices, policies, managedIndices, rollups, transforms } from "../server/routes";
export class IndexPatternManagementPlugin implements Plugin {
public async setup(core: CoreSetup) {
@@ -31,7 +31,8 @@ export class IndexPatternManagementPlugin implements Plugin>> => {
+ try {
+ const { from, size, search, sortDirection, sortField } = request.query as {
+ from: number;
+ size: number;
+ search: string;
+ sortDirection: string;
+ sortField: string;
+ };
+
+ const transformSortMap: { [key: string]: string } = {
+ "_id": "transform.transform_id.keyword",
+ "transform.source_index": "transform.source_index.keyword",
+ "transform.target_index": "transform.target_index.keyword",
+ "transform.transform.enabled": "transform.enabled",
+ };
+
+ // TODO: Correct the parsing
+ const params = {
+ // from: parseInt(from, 10),
+ // size: parseInt(size, 10),
+ // search,
+ // sortField: transformSortMap[sortField] || transformSortMap._id,
+ // sortDirection,
+ };
+
+ const { callAsCurrentUser: callWithRequest } = this.esDriver.asScoped(request);
+ const getTransformsResponse = await callWithRequest("ism.getTransforms", params);
+ const totalTransforms = getTransformsResponse.total_transforms;
+ const transforms = getTransformsResponse.transforms.map((transform: DocumentTransform) => ({
+ seqNo: transform._seqNo as number,
+ primaryTerm: transform._primaryTerm as number,
+ id: transform._id,
+ transform: transform.transform,
+ metadata: null
+ }));
+
+ return response.custom({
+ statusCode: 200,
+ body: {ok: true, response: {transforms: transforms, totalTransforms: totalTransforms, metadata: {}}},
+ });
+ } catch (err) {
+ return response.custom({
+ statusCode: 200,
+ body: {
+ ok: false,
+ error: "Error in getTransforms" + err.message,
+ }
+ })
+ }
+ };
+}
diff --git a/server/services/index.ts b/server/services/index.ts
index a503f77c4..a7ca6d701 100644
--- a/server/services/index.ts
+++ b/server/services/index.ts
@@ -17,5 +17,6 @@ import IndexService from "./IndexService";
import PolicyService from "./PolicyService";
import ManagedIndexService from "./ManagedIndexService";
import RollupService from "./RollupService";
+import TransformService from "./TransformService";
-export { IndexService, PolicyService, ManagedIndexService, RollupService };
+export { IndexService, PolicyService, ManagedIndexService, RollupService, TransformService };
diff --git a/server/utils/constants.ts b/server/utils/constants.ts
index d950b4719..b8f156402 100644
--- a/server/utils/constants.ts
+++ b/server/utils/constants.ts
@@ -17,6 +17,7 @@ import { DefaultHeaders, IndexManagementApi } from "../models/interfaces";
export const API_ROUTE_PREFIX = "/_opendistro/_ism";
export const API_ROUTE_PREFIX_ROLLUP = "/_opendistro/_rollup";
+export const TRANSFORM_ROUTE_PREFIX = "/_opendistro/_transform";
export const API: IndexManagementApi = {
POLICY_BASE: `${API_ROUTE_PREFIX}/policies`,
@@ -26,6 +27,7 @@ export const API: IndexManagementApi = {
REMOVE_POLICY_BASE: `${API_ROUTE_PREFIX}/remove`,
CHANGE_POLICY_BASE: `${API_ROUTE_PREFIX}/change_policy`,
ROLLUP_JOBS_BASE: `${API_ROUTE_PREFIX_ROLLUP}/jobs`,
+ TRANSFORM_BASE: `${TRANSFORM_ROUTE_PREFIX}`,
};
export const DEFAULT_HEADERS: DefaultHeaders = {
From 568c1396ea0f78f27607c433daed221cfaff553d Mon Sep 17 00:00:00 2001
From: Annie
Date: Tue, 27 Apr 2021 15:15:17 -0700
Subject: [PATCH 07/93] Update step2 title
---
.../components/CreateTransformSteps/CreateTransformSteps.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx b/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
index 02bdc136b..9d42e6e9a 100644
--- a/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
+++ b/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
@@ -26,7 +26,7 @@ const setOfSteps = (step: number) => {
children: null,
},
{
- title: "Define aggregations and metrics",
+ title: "Define transforms",
children: null,
status: step < 2 ? "disabled" : null,
},
From 479c3326d55ac128c0ac05c087ef2d8cba083f88 Mon Sep 17 00:00:00 2001
From: Annie
Date: Tue, 27 Apr 2021 15:28:33 -0700
Subject: [PATCH 08/93] Add DefineTransform component panel
---
.../DefineTransforms/DefineTransforms.tsx | 35 +++++++++++++++++++
.../components/DefineTransforms/index.ts | 18 ++++++++++
.../CreateTransformForm.tsx | 7 ++--
.../CreateTransformStep2.tsx | 14 ++++----
4 files changed, 63 insertions(+), 11 deletions(-)
create mode 100644 public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
create mode 100644 public/pages/CreateTransform/components/DefineTransforms/index.ts
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
new file mode 100644
index 000000000..47ea523b9
--- /dev/null
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component } from "react";
+import { ContentPanel } from "../../../../components/ContentPanel";
+
+interface DefineTransformsProps {
+ transformId: string;
+}
+
+interface DefineTransformsState {}
+
+export default class DefineTransforms extends Component {
+ constructor(props: DefineTransformsProps) {
+ super(props);
+ const { transfromId } = this.props;
+ this.state = {};
+ }
+
+ render() {
+ return ;
+ }
+}
diff --git a/public/pages/CreateTransform/components/DefineTransforms/index.ts b/public/pages/CreateTransform/components/DefineTransforms/index.ts
new file mode 100644
index 000000000..ac624d7f2
--- /dev/null
+++ b/public/pages/CreateTransform/components/DefineTransforms/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import DefineTransforms from "./DefineTransforms";
+
+export default DefineTransforms;
diff --git a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
index 16c135c2c..950e8db45 100644
--- a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
@@ -193,7 +193,7 @@ export default class CreateTransformForm extends Component
-
+
@@ -53,9 +54,10 @@ export default class CreateTransformStep2 extends Component
- Definition Placeholder
+ Define transform
+
From 71bb63664f11c8754a183afccbe5e836baafbcd4 Mon Sep 17 00:00:00 2001
From: Annie
Date: Tue, 27 Apr 2021 16:03:04 -0700
Subject: [PATCH 09/93] Add full screen button
---
.../DefineTransforms/DefineTransforms.tsx | 29 +++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index 47ea523b9..d47dbc063 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -14,7 +14,7 @@
*/
import React, { Component } from "react";
-import { ContentPanel } from "../../../../components/ContentPanel";
+import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
interface DefineTransformsProps {
transformId: string;
@@ -30,6 +30,31 @@ export default class DefineTransforms extends Component;
+ return (
+
+ // onShow(ApplyPolicyModal, {
+ // indices: selectedItems.map((item: ManagedCatIndex) => item.index),
+ // core: this.context,
+ // }),
+ },
+ },
+ ]}
+ />
+ }
+ bodyStyles={{ padding: "initial" }}
+ title="Select fields to transform"
+ titleSize="m"
+ >
+ );
}
}
From f941b1e25a8fb6d93952a3d116b8315b580891d5 Mon Sep 17 00:00:00 2001
From: Ravi Thaluru
Date: Wed, 28 Apr 2021 11:37:59 -0700
Subject: [PATCH 10/93] Adding working GetTransform page
---
.../containers/Transforms/Transforms.tsx | 167 +++++++++++---
public/services/TransformService.ts | 6 +-
server/clusters/ism/ismPlugin.ts | 60 ++++-
server/models/interfaces.ts | 2 +-
server/routes/rollups.ts | 2 +-
server/routes/transforms.ts | 48 ++++
server/services/TransformService.ts | 207 +++++++++++++++++-
7 files changed, 443 insertions(+), 49 deletions(-)
diff --git a/public/pages/Transforms/containers/Transforms/Transforms.tsx b/public/pages/Transforms/containers/Transforms/Transforms.tsx
index 3a291798e..25d8c54de 100644
--- a/public/pages/Transforms/containers/Transforms/Transforms.tsx
+++ b/public/pages/Transforms/containers/Transforms/Transforms.tsx
@@ -14,6 +14,8 @@
*/
import {
+ // @ts-ignore
+ Criteria,
Direction,
EuiPanel,
EuiFlexGroup,
@@ -38,18 +40,19 @@ import {
import queryString from "query-string";
import { RouteComponentProps } from "react-router-dom";
import TransformService from "../../../../services/TransformService";
-import {DocumentTransform} from "../../../../../models/interfaces";
+import { DocumentTransform } from "../../../../../models/interfaces";
import React, { Component } from "react";
import { CoreServicesContext } from "../../../../components/core_services";
-import {getURLQueryParams} from "../../utils/helpers";
-import {TransformQueryParams} from "../../models/interfaces";
-import {getErrorMessage} from "../../../../utils/helpers";
-import {BREADCRUMBS, ROUTES} from "../../../../utils/constants";
+import { getURLQueryParams } from "../../utils/helpers";
+import { TransformQueryParams } from "../../models/interfaces";
+import { getErrorMessage } from "../../../../utils/helpers";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
import DeleteModal from "../../components/DeleteModal";
import TransformEmptyPrompt from "../../components/TransformEmptyPrompt";
-import {renderEnabled, renderStatus} from "../../utils/metadataHelper";
-import {DEFAULT_PAGE_SIZE_OPTIONS} from "../../../Indices/utils/constants";
+import { renderEnabled, renderStatus } from "../../utils/metadataHelper";
+import {DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_QUERY_PARAMS} from "../../../Indices/utils/constants";
import _ from "lodash";
+import { ManagedCatIndex } from "../../../../../server/models/interfaces";
interface TransformProps extends RouteComponentProps {
transformService: TransformService
@@ -86,7 +89,7 @@ export default class Transforms extends Component => {
+ getTransforms = async() => {
this.setState( { fetchingTransforms: true });
try {
const { transformService, history } = this.props;
@@ -333,25 +344,125 @@ export default class Transforms extends Component {
+ this.state.selectedItems.map((item: DocumentTransform) => { return item._id }).join(", ");
+ };
+
+ onSelectionChange = (selectedItems: DocumentTransform[]): void => {
+ this.setState({ selectedItems });
+ };
+
+ showDeleteModal = () => {
+ this.setState({ isDeleteModalVisible: true });
+ };
+
+ closeDeleteModal = () => {
+ this.setState({ isDeleteModalVisible: false });
+ };
+
+ onClickCreate = () => {
+ this.props.history.push(ROUTES.CREATE_TRANSFORM);
+ };
+
+ onClickEdit = () => {
+ const { selectedItems: [{_id}] } = this.state;
+ if (_id) this.props.history.push(`${ROUTES.EDIT_TRANSFORM}?id=${_id}`);
+ };
+
+ onClickDelete = async() => {
+ const { transformService } = this.props;
+ const { selectedItems } = this.state;
+ for (let item of selectedItems) {
+ const transformId = item._id;
+ try {
+ const response = await transformService.deleteTransform(transformId);
+
+ if (response.ok) {
+ this.closeDeleteModal();
+ this.context.notification.toasts.addSuccess(`"${transformId}" successfully deleted!`);
+ } else {
+ this.context.notifications.toasts.addDanger(`could not delete transform job "${transformId}" : ${response.error}`);
+ }
+ } catch (err) {
+ this.context.notification.toasts.addDanger(getErrorMessage(err, "Could not delete the transform job"));
+ }
+ }
+
+ await this.getTransforms();
+ };
+
+ onPageClick = (page: number) => {
+ this.setState({from: page * this.state.size });
+ };
+
+ onTableChange = ({ page: tablePage, sort }: Criteria) => {
+ const { index: page, size } = tablePage;
+ const { field: sortField, direction: sortDirection } = sort;
+ this.setState({ from: page * size, size, sortField, sortDirection });
+ };
+
+ closePopover = () => {
+ this.setState({ isPopOverOpen: false });
+ };
+
+ resetFilters = () => {
+ this.setState({ search: DEFAULT_QUERY_PARAMS.search });
+ };
+
+ onEnable = async() => {
+ const { transformService } = this.props;
+ const { selectedItems } = this.state;
+
+ for (const item of selectedItems) {
+ const transformId = item._id;
+ try {
+ const response = await transformService.startTransform(transformId);
+
+ if (response.ok) {
+ this.context.notifications.toasts.addSuccess(`${transformId} is enabled`);
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not start transform job "${transformId}": ${response.error}`);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, `Could not start transform job ${transformId}`))
+ }
+ }
+
+ await this.getTransforms();
+ };
+
+ onDisable = async() => {
+ const { transformService } = this.props;
+ const { selectedItems } = this.state;
+
+ for (const item of selectedItems) {
+ const transformId = item._id;
+ try {
+ const response = await transformService.stopTransform(transformId);
+
+ if (response.ok) {
+ this.context.notifications.toasts.addSuccess(`${transformId} is disabled`);
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not stop transform job "${transformId}": ${response.error}`);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, `Could not stop transform job ${transformId}`))
+ }
+ }
+
+ await this.getTransforms();
+ };
+
+ onSearchChange = (e: React.ChangeEvent) => {
+ this.setState({ from: 0, search: e.target.value });
+ };
+
+ onActionButtonClick = () => {
+ this.setState({ isPopOverOpen: !this.state.isPopOverOpen });
+ };
+
+ static getQueryObjectFromState({ from, size, search, sortField, sortDirection} : TransformState) : TransformQueryParams {
+ return { from, size, search, sortField, sortDirection };
}
}
diff --git a/public/services/TransformService.ts b/public/services/TransformService.ts
index 0e3ebb166..b31a49fa3 100644
--- a/public/services/TransformService.ts
+++ b/public/services/TransformService.ts
@@ -15,7 +15,7 @@
import {HttpSetup} from "kibana/public";
import {ServerResponse} from "../../server/models/types";
-import {GetTransformResponse, PutTransformResponse} from "../../server/models/interfaces";
+import {GetTransformsResponse, PutTransformResponse} from "../../server/models/interfaces";
import {NODE_API} from "../../utils/constants";
import {DocumentTransform, Transform} from "../../models/interfaces";
@@ -26,10 +26,10 @@ export default class TransformService {
this.httpClient = httpClient
}
- getTransforms = async (queryObject: object): Promise> => {
+ getTransforms = async (queryObject: object): Promise> => {
const url = `..${NODE_API.TRANSFORMS}`
// @ts-ignore
- return (await this.httpClient.get(url, { query: queryObject })) as ServerResponse;
+ return (await this.httpClient.get(url, { query: queryObject })) as ServerResponse;
};
putTransform = async (
diff --git a/server/clusters/ism/ismPlugin.ts b/server/clusters/ism/ismPlugin.ts
index 8bcd7101c..4ca75fb72 100644
--- a/server/clusters/ism/ismPlugin.ts
+++ b/server/clusters/ism/ismPlugin.ts
@@ -287,9 +287,61 @@ export default function ismPlugin(Client: any, config: any, components: any) {
});
ism.getTransforms = ca({
- url: {
- fmt: `${API.TRANSFORM_BASE}/`,
- },
- method: "GET",
+ url: {
+ fmt: `${API.TRANSFORM_BASE}/`,
+ },
+ method: "GET",
+ });
+
+ ism.explainTransform = ca({
+ url: {
+ fmt: `${API.TRANSFORM_BASE}/<%=transformId%>/_explain`,
+ req: {
+ transformId: {
+ type: "string",
+ required: true,
+ }
+ }
+ },
+ method: "GET",
+ });
+
+ ism.startTransform = ca({
+ url: {
+ fmt: `${API.TRANSFORM_BASE}/<%=transformId%>/_start`,
+ req: {
+ transformId: {
+ type: "string",
+ required: true,
+ }
+ }
+ },
+ method: "POST",
+ });
+
+ ism.stopTransform = ca({
+ url: {
+ fmt: `${API.TRANSFORM_BASE}/<%=transformId%>/_stop`,
+ req: {
+ transformId: {
+ type: "string",
+ required: true,
+ }
+ }
+ },
+ method: "POST",
+ });
+
+ ism.deleteTransform = ca({
+ url: {
+ fmt: `${API.TRANSFORM_BASE}/<%=transformId%>`,
+ req: {
+ transformId: {
+ type: "string",
+ required: true,
+ }
+ }
+ },
+ method: "DELETE"
});
}
diff --git a/server/models/interfaces.ts b/server/models/interfaces.ts
index 636e9e2de..3269f07be 100644
--- a/server/models/interfaces.ts
+++ b/server/models/interfaces.ts
@@ -100,7 +100,7 @@ export interface DeleteTransformResponse {
result: string;
}
-export interface GetTransformResponse {
+export interface GetTransformsResponse {
transforms: DocumentTransform[];
totalTransforms: number;
metadata: any;
diff --git a/server/routes/rollups.ts b/server/routes/rollups.ts
index 9e32e397a..7ec4e86de 100644
--- a/server/routes/rollups.ts
+++ b/server/routes/rollups.ts
@@ -19,7 +19,7 @@ import { NodeServices } from "../models/interfaces";
import { NODE_API } from "../../utils/constants";
export default function (services: NodeServices, router: IRouter) {
- const { rollupService } = services;
+ const { rollupService, transformService } = services;
router.get(
{
diff --git a/server/routes/transforms.ts b/server/routes/transforms.ts
index d987aa569..ce7dd0de3 100644
--- a/server/routes/transforms.ts
+++ b/server/routes/transforms.ts
@@ -36,4 +36,52 @@ export default function (services: NodeServices, router: IRouter) {
},
transformService.getTransforms
);
+
+ router.get(
+ {
+ path: `${NODE_API.TRANSFORMS}/{id}`,
+ validate: {
+ params: schema.object({
+ id: schema.string(),
+ }),
+ },
+ },
+ transformService.getTransform
+ );
+
+ router.post(
+ {
+ path: `${NODE_API.TRANSFORMS}/{id}/_stop`,
+ validate: {
+ params: schema.object({
+ id: schema.string(),
+ }),
+ },
+ },
+ transformService.stopTransform
+ );
+
+ router.post(
+ {
+ path: `${NODE_API.TRANSFORMS}/{id}/_start`,
+ validate: {
+ params: schema.object({
+ id: schema.string(),
+ }),
+ },
+ },
+ transformService.startTransform
+ );
+
+ router.delete(
+ {
+ path: `${NODE_API.TRANSFORMS}/{id}`,
+ validate: {
+ params: schema.object({
+ id: schema.string(),
+ }),
+ },
+ },
+ transformService.deleteTransform
+ )
}
diff --git a/server/services/TransformService.ts b/server/services/TransformService.ts
index 383f45c82..4a1774947 100644
--- a/server/services/TransformService.ts
+++ b/server/services/TransformService.ts
@@ -21,8 +21,9 @@ import {
RequestHandlerContext
} from "kibana/server";
import {ServerResponse} from "../models/types";
-import {GetTransformResponse} from "../models/interfaces";
-import {DocumentTransform} from "../../models/interfaces";
+import {GetTransformsResponse} from "../models/interfaces";
+import {DocumentTransform, Transform} from "../../models/interfaces";
+import _ from "lodash";
export default class TransformService {
esDriver: IClusterClient;
@@ -35,7 +36,7 @@ export default class TransformService {
context: RequestHandlerContext,
request: KibanaRequest,
response: KibanaResponseFactory
- ): Promise>> => {
+ ): Promise>> => {
try {
const { from, size, search, sortDirection, sortField } = request.query as {
from: number;
@@ -52,31 +53,59 @@ export default class TransformService {
"transform.transform.enabled": "transform.enabled",
};
- // TODO: Correct the parsing
const params = {
- // from: parseInt(from, 10),
- // size: parseInt(size, 10),
- // search,
- // sortField: transformSortMap[sortField] || transformSortMap._id,
- // sortDirection,
+ from: parseInt(from, 10),
+ size: parseInt(size, 10),
+ search,
+ sortField: transformSortMap[sortField] || transformSortMap._id,
+ sortDirection,
};
const { callAsCurrentUser: callWithRequest } = this.esDriver.asScoped(request);
const getTransformsResponse = await callWithRequest("ism.getTransforms", params);
const totalTransforms = getTransformsResponse.total_transforms;
const transforms = getTransformsResponse.transforms.map((transform: DocumentTransform) => ({
- seqNo: transform._seqNo as number,
- primaryTerm: transform._primaryTerm as number,
- id: transform._id,
+ _seqNo: transform._seqNo as number,
+ _primaryTerm: transform._primaryTerm as number,
+ _id: transform._id,
transform: transform.transform,
metadata: null
}));
+ if (totalTransforms) {
+ const ids = transforms.map((transform: DocumentTransform) => transform._id).join(",");
+ const explainResponse = await callWithRequest("ism.explainTransform", { transformId: ids });
+ if (!explainResponse.error) {
+ transforms.map((transform: DocumentTransform) => {
+ transform.metadata = explainResponse[transform._id];
+ });
+
+ return response.custom({
+ statusCode: 200,
+ body: { ok: true, response: {transforms: transforms, totalTransforms: totalTransforms, metadata: explainResponse} },
+ });
+ } else {
+ return response.custom({
+ statusCode: 200,
+ body: {
+ ok: false,
+ error: explainResponse ? explainResponse.error : "An error occurred when calling getExplain API.",
+ }
+ });
+ }
+ }
return response.custom({
statusCode: 200,
body: {ok: true, response: {transforms: transforms, totalTransforms: totalTransforms, metadata: {}}},
});
} catch (err) {
+ if (err.statusCode === 404 && err.body.error.type === "index_not_found_exception") {
+ return response.custom({
+ statusCode: 200,
+ body: { ok: true, response: { transforms: [], totalTransforms: 0, metadata: null } },
+ });
+ }
+ console.error("Index Management - TransformService - getTransforms", err);
return response.custom({
statusCode: 200,
body: {
@@ -86,4 +115,158 @@ export default class TransformService {
})
}
};
+
+ getTransform = async(
+ context: RequestHandlerContext,
+ request: KibanaRequest,
+ response: KibanaResponseFactory
+ ): Promise>> => {
+ try {
+ const { id } = request.params as { id: string };
+ const params = { transformId: id };
+ const { callAsCurrentUser: callWithRequest } = this.esDriver.asScoped(request);
+ const getResponse = await callWithRequest("ism.getTransform", params);
+ const metadata = await callWithRequest("ism.explainTransform", params);
+ const transform = _.get(getResponse, "transform", null);
+ const seqNo = _.get(getResponse, "_seg_no", null);
+ const primaryTerm = _.get(getResponse, "_primary_term", null);
+
+ if (transform) {
+ if (metadata) {
+ return response.custom({
+ statusCode: 200,
+ body: {
+ ok: true,
+ response: {
+ _id: id,
+ _seqNo: seqNo as number,
+ _primaryTerm: primaryTerm as number,
+ transform: transform as Transform,
+ metadata: metadata,
+ }
+ }
+ });
+ } else {
+ return response.custom({
+ statusCode: 200,
+ body: {
+ ok: false,
+ error: "Failed to load metadata for transform",
+ }
+ });
+ }
+ } else {
+ return response.custom({
+ statusCode: 200,
+ body: {
+ ok: false,
+ error: "Failed to load transform",
+ }
+ });
+ }
+ } catch (err) {
+ console.error("Index Management - TransformService - getTransform:", err);
+ return response.custom({
+ statusCode: 200,
+ body: {
+ ok: false,
+ error: err.message,
+ },
+ });
+ }
+ };
+
+ startTransform = async(
+ context: RequestHandlerContext,
+ request: KibanaRequest,
+ response: KibanaResponseFactory
+ ): Promise>> => {
+ try {
+ const { id } = request.params as { id: string };
+ console.log("received "+ JSON.stringify(request.params));
+ const params = { transformId: id };
+ const { callAsCurrentUser: callWithRequest } = this.esDriver.asScoped(request);
+ const startResponse = await callWithRequest("ism.startTransform", params);
+ const acknowledged = _.get(startResponse, "acknowledged");
+ if (acknowledged) {
+ return response.custom({
+ statusCode: 200,
+ body: { ok: true, response: true },
+ });
+ } else {
+ return response.custom({
+ statusCode: 200,
+ body: { ok: false, error: "Failed to start transform" },
+ });
+ }
+ } catch (err) {
+ console.error("Index Management - TransformService - startTransform", err);
+ return response.custom({
+ statusCode: 200,
+ body: { ok: false, error: err.message },
+ });
+ }
+ };
+
+ stopTransform = async(
+ context: RequestHandlerContext,
+ request: KibanaRequest,
+ response: KibanaResponseFactory
+ ): Promise>> => {
+ try {
+ const { id } = request.params as { id: string };
+ const params = { transformId: id };
+ const { callAsCurrentUser: callWithRequest } = this.esDriver.asScoped(request);
+ const stopResponse = await callWithRequest("ism.stopTransform", params);
+ const acknowledged = _.get(stopResponse, "acknowledged");
+ if (acknowledged) {
+ return response.custom({
+ statusCode: 200,
+ body: { ok: true, response: true },
+ });
+ } else {
+ return response.custom({
+ statusCode: 200,
+ body: { ok: false, error: "Failed to stop transform" },
+ });
+ }
+ } catch (err) {
+ console.error("Index Management - TransformService - stopTransform", err);
+ return response.custom({
+ statusCode: 200,
+ body: { ok: false, error: err.message },
+ });
+ }
+ };
+
+ deleteTransform = async(
+ context: RequestHandlerContext,
+ request: KibanaRequest,
+ response: KibanaResponseFactory
+ ): Promise>> => {
+ try {
+ const { id } = request.params as { id: string };
+ const params = { transformId: id };
+ const { callAsCurrentUser: callWithRequest } = this.esDriver.asScoped(request);
+ const deleteResponse = await callWithRequest("ism.deleteTransform", params);
+ const acknowledged = _.get(deleteResponse, "acknowledged");
+ if (acknowledged) {
+ return response.custom({
+ statusCode: 200,
+ body: { ok: true, response: true },
+ });
+ } else {
+ return response.custom({
+ statusCode: 200,
+ body: { ok: false, error: "Failed to delete transform" },
+ });
+ }
+ } catch (err) {
+ console.error("Index Management - TransformService - deleteTransform", err);
+ return response.custom({
+ statusCode: 200,
+ body: { ok: false, error: err.message },
+ })
+ }
+ }
}
From ccdf22819fce7b787a7a70a27d7ac48a84920319 Mon Sep 17 00:00:00 2001
From: Ravi Thaluru
Date: Wed, 28 Apr 2021 17:02:02 -0700
Subject: [PATCH 11/93] Adding edit/details pages for transforms
---
public/pages/Main/Main.tsx | 27 +-
.../ConfigureTransform/Configure.tsx | 57 ++++
.../components/ConfigureTransform/index.ts | 18 ++
.../GeneralInformation/GeneralInformation.tsx | 85 +++++
.../components/GeneralInformation/index.ts | 18 ++
.../TransformStatus/TransformStatus.tsx | 91 ++++++
.../components/TransformStatus/index.ts | 18 ++
.../containers/Transforms/EditTransform.tsx | 140 +++++++++
.../Transforms/TransformDetails.tsx | 292 ++++++++++++++++++
.../containers/Transforms/Transforms.tsx | 35 ++-
.../Transforms/containers/Transforms/index.ts | 19 ++
public/pages/Transforms/index.ts | 18 ++
public/pages/Transforms/utils/constants.tsx | 22 +-
public/pages/Transforms/utils/helpers.ts | 13 +-
14 files changed, 821 insertions(+), 32 deletions(-)
create mode 100644 public/pages/Transforms/components/ConfigureTransform/Configure.tsx
create mode 100644 public/pages/Transforms/components/ConfigureTransform/index.ts
create mode 100644 public/pages/Transforms/components/GeneralInformation/GeneralInformation.tsx
create mode 100644 public/pages/Transforms/components/GeneralInformation/index.ts
create mode 100644 public/pages/Transforms/components/TransformStatus/TransformStatus.tsx
create mode 100644 public/pages/Transforms/components/TransformStatus/index.ts
create mode 100644 public/pages/Transforms/containers/Transforms/EditTransform.tsx
create mode 100644 public/pages/Transforms/containers/Transforms/TransformDetails.tsx
create mode 100644 public/pages/Transforms/containers/Transforms/index.ts
create mode 100644 public/pages/Transforms/index.ts
diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx
index 1f2f426e3..c48320e83 100644
--- a/public/pages/Main/Main.tsx
+++ b/public/pages/Main/Main.tsx
@@ -32,7 +32,8 @@ import { CoreServicesConsumer } from "../../components/core_services";
import CreateRollupForm from "../CreateRollup/containers/CreateRollupForm";
import EditRollup from "../EditRollup/containers";
import RollupDetails from "../RollupDetails/containers/RollupDetails";
-import Transforms from "../Transforms/containers/Transforms/Transforms";
+import { EditTransform, Transforms } from "../Transforms";
+import TransformDetails from "../Transforms/containers/Transforms/TransformDetails";
enum Navigation {
IndexManagement = "Index Management",
@@ -92,7 +93,7 @@ export default class Main extends Component {
name: Navigation.Transforms,
id: 5,
href: `#${Pathname.Transforms}`,
- isSelected: pathname === Pathname.Transforms
+ isSelected: pathname === Pathname.Transforms,
},
],
},
@@ -108,11 +109,15 @@ export default class Main extends Component {
{/*Hide side navigation bar when creating or editing rollup job*/}
- {pathname != ROUTES.CREATE_ROLLUP && pathname != ROUTES.EDIT_ROLLUP && pathname != ROUTES.ROLLUP_DETAILS && (
-
-
-
- )}
+ {pathname != ROUTES.CREATE_ROLLUP &&
+ pathname != ROUTES.EDIT_ROLLUP &&
+ pathname != ROUTES.ROLLUP_DETAILS &&
+ pathname != ROUTES.EDIT_TRANSFORM &&
+ pathname != ROUTES.TRANSFORM_DETAILS && (
+
+
+
+ )}
{
/>
(
+ render={(props: RouteComponentProps) => (
-
+
)}
/>
@@ -213,7 +218,7 @@ export default class Main extends Component {
path={ROUTES.EDIT_TRANSFORM}
render={(props: RouteComponentProps) => (
-
+
)}
/>
@@ -221,7 +226,7 @@ export default class Main extends Component {
path={ROUTES.TRANSFORM_DETAILS}
render={(props: RouteComponentProps) => (
-
+
)}
/>
diff --git a/public/pages/Transforms/components/ConfigureTransform/Configure.tsx b/public/pages/Transforms/components/ConfigureTransform/Configure.tsx
new file mode 100644
index 000000000..f014fb233
--- /dev/null
+++ b/public/pages/Transforms/components/ConfigureTransform/Configure.tsx
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent } from "react";
+import { EuiSpacer, EuiFormRow, EuiFieldText, EuiTextArea, EuiText, EuiFlexGroup, EuiFlexItem } from "@elastic/eui";
+import { ContentPanel } from "../../../../components/ContentPanel";
+
+interface ConfigureTransformProps {
+ inEdit: boolean;
+ id: string;
+ error: string;
+ onChangeName: (value: ChangeEvent) => void;
+ onChangeDescription: (value: ChangeEvent) => void;
+ description: string;
+}
+
+const ConfigureTransform = ({ inEdit, id, error, onChangeName, onChangeDescription, description }: ConfigureTransformProps) => (
+
+
+
+
+
+
+
+
+
+
+ Description
+
+
+
+
+ - optional
+
+
+
+
+
+
+
+
+
+);
+
+export default ConfigureTransform;
diff --git a/public/pages/Transforms/components/ConfigureTransform/index.ts b/public/pages/Transforms/components/ConfigureTransform/index.ts
new file mode 100644
index 000000000..fd7952e60
--- /dev/null
+++ b/public/pages/Transforms/components/ConfigureTransform/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import ConfigureTransform from "./Configure";
+
+export default ConfigureTransform;
diff --git a/public/pages/Transforms/components/GeneralInformation/GeneralInformation.tsx b/public/pages/Transforms/components/GeneralInformation/GeneralInformation.tsx
new file mode 100644
index 000000000..fe6990559
--- /dev/null
+++ b/public/pages/Transforms/components/GeneralInformation/GeneralInformation.tsx
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component } from "react";
+import { EuiFlexGrid, EuiSpacer, EuiFlexItem, EuiText } from "@elastic/eui";
+import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
+import { ModalConsumer } from "../../../../components/Modal";
+
+interface GeneralInformationProps {
+ id: string;
+ description: string;
+ sourceIndex: string;
+ targetIndex: string;
+ scheduledText: string;
+ pageSize: number;
+ updatedAt: number;
+ onEdit: () => void;
+}
+
+export default class GenerationInformation extends Component {
+ constructor(props: GeneralInformationProps) {
+ super(props);
+ }
+
+ render() {
+ const { id, description, sourceIndex, targetIndex, scheduledText, pageSize, updatedAt, onEdit } = this.props;
+ const infoItems = [
+ { term: "Name", value: id },
+ { term: "Source index", value: sourceIndex },
+ { term: "Target index", value: targetIndex },
+ { term: "Schedule", value: scheduledText },
+ { term: "Description", value: description || "-" },
+ { term: "UpdatedAt", value: updatedAt },
+ { term: "Pages per execution", value: pageSize },
+ ];
+
+ return (
+
+ {() => (
+ onEdit(),
+ },
+ },
+ ]}
+ />
+ )}
+
+ }
+ >
+
+
+
+ {infoItems.map((item) => (
+
+
+ {item.term}
+ {item.value}
+
+
+ ))}
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/Transforms/components/GeneralInformation/index.ts b/public/pages/Transforms/components/GeneralInformation/index.ts
new file mode 100644
index 000000000..dd311c3f6
--- /dev/null
+++ b/public/pages/Transforms/components/GeneralInformation/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import GeneralInformation from "./GeneralInformation";
+
+export default GeneralInformation;
diff --git a/public/pages/Transforms/components/TransformStatus/TransformStatus.tsx b/public/pages/Transforms/components/TransformStatus/TransformStatus.tsx
new file mode 100644
index 000000000..77343c4f7
--- /dev/null
+++ b/public/pages/Transforms/components/TransformStatus/TransformStatus.tsx
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component } from "react";
+import { EuiFlexGrid, EuiSpacer, EuiFlexItem, EuiText } from "@elastic/eui";
+import { TransformMetadata } from "../../../../../models/interfaces";
+import { ContentPanel } from "../../../../components/ContentPanel";
+import { renderStatus } from "../../utils/metadataHelper";
+
+interface TransformStatusProps {
+ metadata: TransformMetadata | undefined;
+}
+export default class TransformStatus extends Component {
+ constructor(props: TransformStatusProps) {
+ super(props);
+ }
+
+ render() {
+ const { metadata } = this.props;
+ return (
+
+
+
+
+
+
+ Status
+ {renderStatus(metadata)}
+
+
+
+
+ Documents indexed
+
+ {metadata == null || metadata.transform_metadata == null ? "-" : metadata.transform_metadata.stats.documents_indexed}
+
+
+
+
+
+ Indexed time (ms)
+
+ {metadata == null || metadata.transform_metadata == null ? "-" : metadata.transform_metadata.stats.index_time_in_millis}
+
+
+
+
+
+
+
+ Document processed
+
+ {metadata == null || metadata.transform_metadata == null ? "-" : metadata.transform_metadata.stats.documents_processed}
+
+
+
+
+
+ Search time (ms)
+
+ {metadata == null || metadata.transform_metadata == null ? "-" : metadata.transform_metadata.stats.search_time_in_millis}
+
+
+
+
+
+
+
+ Page processed
+ {metadata == null || metadata.transform_metadata == null ? "-" : metadata.transform_metadata.stats.pages_processed}
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/Transforms/components/TransformStatus/index.ts b/public/pages/Transforms/components/TransformStatus/index.ts
new file mode 100644
index 000000000..8f2f8df70
--- /dev/null
+++ b/public/pages/Transforms/components/TransformStatus/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import TransformStatus from "./TransformStatus";
+
+export default TransformStatus;
diff --git a/public/pages/Transforms/containers/Transforms/EditTransform.tsx b/public/pages/Transforms/containers/Transforms/EditTransform.tsx
new file mode 100644
index 000000000..897f3db56
--- /dev/null
+++ b/public/pages/Transforms/containers/Transforms/EditTransform.tsx
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import { RouteComponentProps } from "react-router-dom";
+import { TransformService } from "../../../../services";
+import { CoreServicesContext } from "../../../../components/core_services";
+import React, { Component } from "react";
+import { EMPTY_TRANSFORM } from "../../utils/constants";
+import queryString from "query-string";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import { getErrorMessage } from "../../../../utils/helpers";
+import { EuiFlexItem, EuiFlexGroup, EuiButton, EuiTitle, EuiSpacer, EuiButtonEmpty } from "@elastic/eui";
+import ConfigureTransform from "../../components/ConfigureTransform";
+
+interface EditTransformProps extends RouteComponentProps {
+ transformService: TransformService;
+}
+
+interface EditTransformState {
+ id: string;
+ error: string;
+ seqNo: number | null;
+ primaryTerm: number | null;
+ description: string;
+ pageSize: number;
+ enabled: boolean;
+ transformJSON: any;
+ isLoading: boolean;
+}
+
+export default class EditTransform extends Component {
+ static contextType = CoreServicesContext;
+
+ constructor(props: EditTransformProps) {
+ super(props);
+ this.state = {
+ id: "",
+ error: "",
+ seqNo: null,
+ primaryTerm: null,
+ description: "",
+ pageSize: 1000,
+ enabled: true,
+ transformJSON: EMPTY_TRANSFORM,
+ isLoading: false,
+ };
+ }
+
+ componentDidMount = async () => {
+ const { id } = queryString.parse(this.props.location.search);
+ if (typeof id === "string" && !!id) {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS, BREADCRUMBS.EDIT_TRANSFORM, { text: id }]);
+ await this.getTransform(id);
+ } else {
+ this.context.notifications.toasts.addDanger(`Invalid transform id: ${id}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ };
+
+ getTransform = async (transformId: string) => {
+ try {
+ const { transformService } = this.props;
+ const response = await transformService.getTransform(transformId);
+
+ if (response.ok) {
+ let json = JSON.parse(this.state.transformJSON);
+ json.transform = response.response.transform;
+
+ this.setState({
+ seqNo: response.response._seqNo,
+ primaryTerm: response.response._primaryTerm,
+ id: response.response._id,
+ description: response.response.transform.description,
+ enabled: response.response.transform.enabled,
+ pageSize: response.response.transform.page_size,
+ transformJSON: json,
+ });
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not load transform job ${transformId}: ${response.error}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, `Could not load transform job ${transformId}`));
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ };
+
+ render() {
+ const { id, error, description, isLoading } = this.state;
+ return (
+
+
+ Edit transform job
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Save changes
+
+
+
+
+ );
+ }
+
+ onCancel = () => {};
+ onSubmit = () => {};
+ onChangeName = () => {};
+ onChangeDescription = () => {};
+}
diff --git a/public/pages/Transforms/containers/Transforms/TransformDetails.tsx b/public/pages/Transforms/containers/Transforms/TransformDetails.tsx
new file mode 100644
index 000000000..a567aae8b
--- /dev/null
+++ b/public/pages/Transforms/containers/Transforms/TransformDetails.tsx
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import {
+ EuiSpacer,
+ EuiTitle,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButton,
+ EuiOverlayMask,
+ EuiButtonEmpty,
+ EuiModalFooter,
+ EuiModal,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiModalBody,
+ EuiCodeBlock,
+ EuiHealth,
+} from "@elastic/eui";
+import { TransformService } from "../../../../services";
+import { RouteComponentProps } from "react-router-dom";
+import React, { Component } from "react";
+import { CoreServicesContext } from "../../../../components/core_services";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import queryString from "query-string";
+import { getErrorMessage } from "../../../../utils/helpers";
+import { DimensionItem, MetricItem, RollupDimensionItem, TransformMetadata } from "../../../../../models/interfaces";
+import DeleteModal from "../../components/DeleteModal";
+import GenerationInformation from "../../components/GeneralInformation";
+import TransformStatus from "../../components/TransformStatus";
+import { EMPTY_TRANSFORM } from "../../utils/constants";
+
+interface TransformDetailsProps extends RouteComponentProps {
+ transformService: TransformService;
+}
+
+interface TransformDetailsState {
+ id: string;
+ description: string;
+ enabled: boolean;
+ enabledAt: number | null;
+ updatedAt: number;
+ pageSize: number;
+ transformJson: any;
+ sourceIndex: string;
+ targetIndex: string;
+ aggregationsShown: MetricItem[];
+ groupsShown: DimensionItem[];
+ metadata: TransformMetadata | undefined;
+ interval: number;
+ intervalTimeUnit: string;
+ cronExpression: string;
+ isModalOpen: boolean;
+ isDeleteModalOpen: boolean;
+}
+
+export default class TransformDetails extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: TransformDetailsProps) {
+ super(props);
+
+ this.state = {
+ id: "",
+ description: "",
+ enabled: false,
+ enabledAt: null,
+ updatedAt: 1,
+ pageSize: 1000,
+ transformJson: EMPTY_TRANSFORM,
+ sourceIndex: "",
+ targetIndex: "",
+ aggregationsShown: [],
+ groupsShown: [],
+ metadata: undefined,
+ interval: 2,
+ intervalTimeUnit: "",
+ cronExpression: "",
+ isModalOpen: false,
+ isDeleteModalOpen: false,
+ };
+ }
+
+ componentDidMount = async (): Promise => {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS]);
+ const { id } = queryString.parse(this.props.location.search);
+ if (typeof id === "string") {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS, { text: id }]);
+ this.props.history.push(`${ROUTES.TRANSFORM_DETAILS}?id=${id}`);
+ await this.getTransform(id);
+ this.forceUpdate();
+ } else {
+ this.context.notifications.toasts.addDanger(`Invalid rollup id: ${id}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ };
+
+ getTransform = async (transformId: string) => {
+ try {
+ const { transformService } = this.props;
+ const response = await transformService.getTransform(transformId);
+
+ if (response.ok) {
+ let json = response.response;
+ // let aggregations = this.parseAggregations(response.response.transform.aggregations);
+ let groups = this.parseGroups(response.response.transform.groups);
+ this.setState({
+ id: response.response._id,
+ description: response.response.transform.description,
+ enabled: response.response.transform.enabled,
+ enabledAt: response.response.transform.enabled_at,
+ updatedAt: response.response.transform.updated_at,
+ pageSize: response.response.transform.page_size,
+ transformJson: json,
+ sourceIndex: response.response.transform.source_index,
+ targetIndex: response.response.transform.target_index,
+ aggregationsShown: [],
+ groupsShown: groups.slice(0, 10),
+ });
+
+ if (response.response.metadata != null) {
+ this.setState({ metadata: response.response.metadata[response.response._id] });
+ }
+ if ("interval" in response.response.transform.schedule) {
+ this.setState({
+ interval: response.response.transform.schedule.interval.period,
+ intervalTimeUnit: response.response.transform.schedule.interval.unit,
+ });
+ } else {
+ this.setState({ cronExpression: response.response.transform.schedule.cron.expression });
+ }
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not load transform job ${transformId}: ${response.error}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, `Could not load transform job ${transformId}`));
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ };
+
+ parseGroups = (groups: RollupDimensionItem[]): DimensionItem[] => {
+ const sourceArray = groups.slice(1, groups.length);
+ if (sourceArray.length == 0) return [];
+ // @ts-ignore
+ return sourceArray.map((group: RollupDimensionItem) => {
+ let sequence = groups.indexOf(group);
+ switch (true) {
+ case group.date_histogram != null:
+ return {
+ sequence: sequence,
+ aggregationMethod: "date_histogram",
+ field: {
+ label: group.date_histogram?.source_field,
+ },
+ interval: group.date_histogram?.interval,
+ };
+ case group.histogram != null:
+ return {
+ sequence: sequence,
+ aggregationMethod: "histogram",
+ field: {
+ label: group.histogram?.source_field,
+ },
+ interval: group.histogram?.interval,
+ };
+ case group.terms != null:
+ return {
+ sequence: sequence,
+ aggregationMethod: "terms",
+ field: {
+ label: group.terms?.source_field,
+ },
+ interval: null,
+ };
+ }
+ });
+ };
+
+ render() {
+ const {
+ id,
+ enabled,
+ updatedAt,
+ description,
+ sourceIndex,
+ targetIndex,
+ pageSize,
+ metadata,
+ transformJson,
+ isDeleteModalOpen,
+ isModalOpen,
+ } = this.state;
+
+ let scheduleText = "At some time";
+ return (
+
+
+
+
+ {id}
+
+
+
+ {enabled ? {"Enabled on " + updatedAt} : Disabled}
+
+
+
+
+
+
+ Disable
+
+
+
+
+ Enable
+
+
+
+ View JSON
+
+
+
+ Delete
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isModalOpen && (
+
+
+
+ {"View JSON of " + id}
+
+
+
+
+ {JSON.stringify(transformJson, null, 4)}
+
+
+
+
+ Close
+
+
+
+ )}
+
+ {isDeleteModalOpen && }
+
+ );
+ }
+
+ closeModal = () => {};
+ closeDeleteModal = () => {};
+ onClickDelete = () => {};
+ onEdit = () => {};
+ onEnable = () => {};
+ onDisable = () => {};
+ showModal = () => {};
+ showDeleteModal = () => {};
+}
diff --git a/public/pages/Transforms/containers/Transforms/Transforms.tsx b/public/pages/Transforms/containers/Transforms/Transforms.tsx
index 25d8c54de..c53b86d59 100644
--- a/public/pages/Transforms/containers/Transforms/Transforms.tsx
+++ b/public/pages/Transforms/containers/Transforms/Transforms.tsx
@@ -50,12 +50,12 @@ import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
import DeleteModal from "../../components/DeleteModal";
import TransformEmptyPrompt from "../../components/TransformEmptyPrompt";
import { renderEnabled, renderStatus } from "../../utils/metadataHelper";
-import {DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_QUERY_PARAMS} from "../../../Indices/utils/constants";
+import { DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_QUERY_PARAMS } from "../../../Indices/utils/constants";
import _ from "lodash";
import { ManagedCatIndex } from "../../../../../server/models/interfaces";
interface TransformProps extends RouteComponentProps {
- transformService: TransformService
+ transformService: TransformService;
}
interface TransformState {
@@ -96,7 +96,7 @@ export default class Transforms extends Component
@@ -324,8 +323,8 @@ export default class Transforms extends Component {
- this.setState( { fetchingTransforms: true });
+ getTransforms = async () => {
+ this.setState({ fetchingTransforms: true });
try {
const { transformService, history } = this.props;
const queryObject = Transforms.getQueryObjectFromState(this.state);
@@ -334,7 +333,7 @@ export default class Transforms extends Component {
- this.state.selectedItems.map((item: DocumentTransform) => { return item._id }).join(", ");
+ return "asd";
+ // this.state.selectedItems.map((item: DocumentTransform) => { return item._id }).join(", ");
};
onSelectionChange = (selectedItems: DocumentTransform[]): void => {
@@ -365,11 +365,13 @@ export default class Transforms extends Component {
- const { selectedItems: [{_id}] } = this.state;
+ const {
+ selectedItems: [{ _id }],
+ } = this.state;
if (_id) this.props.history.push(`${ROUTES.EDIT_TRANSFORM}?id=${_id}`);
};
- onClickDelete = async() => {
+ onClickDelete = async () => {
const { transformService } = this.props;
const { selectedItems } = this.state;
for (let item of selectedItems) {
@@ -392,7 +394,7 @@ export default class Transforms extends Component {
- this.setState({from: page * this.state.size });
+ this.setState({ from: page * this.state.size });
};
onTableChange = ({ page: tablePage, sort }: Criteria) => {
@@ -409,7 +411,7 @@ export default class Transforms extends Component {
+ onEnable = async () => {
const { transformService } = this.props;
const { selectedItems } = this.state;
@@ -424,14 +426,14 @@ export default class Transforms extends Component {
+ onDisable = async () => {
const { transformService } = this.props;
const { selectedItems } = this.state;
@@ -446,7 +448,7 @@ export default class Transforms extends Component {
+ return {
// @ts-ignores
from: isNaN(parseInt(from, 10)) ? DEFAULT_QUERY_PARAMS.from : parseInt(from, 10),
// @ts-ignores
@@ -30,3 +31,9 @@ export function getURLQueryParams(location: { search: string }): TransformQueryP
sortDirection: typeof sortDirection !== "string" ? DEFAULT_QUERY_PARAMS.sortDirection : sortDirection,
};
}
+
+export const renderTime = (time: number): string => {
+ const momentTime = moment(time).local();
+ if (time && momentTime.isValid()) return momentTime.format("MM/DD/YY h:mm a");
+ return "-";
+};
From a6ea779f7b8a18f0096cafed0e49a661c9183ab8 Mon Sep 17 00:00:00 2001
From: Annie
Date: Wed, 28 Apr 2021 17:38:06 -0700
Subject: [PATCH 12/93] Add text
---
.../DefineTransforms/DefineTransforms.tsx | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index d47dbc063..fd21b5ca7 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -13,11 +13,13 @@
* permissions and limitations under the License.
*/
+import { EuiSpacer, EuiText } from "@elastic/eui";
import React, { Component } from "react";
import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
interface DefineTransformsProps {
transformId: string;
+ sourceIndex: string;
}
interface DefineTransformsState {}
@@ -25,11 +27,12 @@ interface DefineTransformsState {}
export default class DefineTransforms extends Component {
constructor(props: DefineTransformsProps) {
super(props);
- const { transfromId } = this.props;
+ const { transfromId, sourceIndex } = this.props;
this.state = {};
}
render() {
+ const { transfromId, sourceIndex } = this.props;
return (
}
- bodyStyles={{ padding: "initial" }}
+ bodyStyles={{ padding: "10px 10px" }}
title="Select fields to transform"
titleSize="m"
- >
+ >
+
+ Original fields with sample data
+
+
+ {/*TODO: Substitute "source index", and "filtered by" fields with actual values*/}
+
+ {`Viewing sample data from index ${sourceIndex}, filtered by order.type:sales_order, order.success:true`}
+
+
);
}
}
From 2b6344949ce9ccea7765cab3caea3c119c9922be Mon Sep 17 00:00:00 2001
From: Annie
Date: Wed, 28 Apr 2021 17:53:20 -0700
Subject: [PATCH 13/93] Pass sourceIndex as string to step2
---
.../components/DefineTransforms/DefineTransforms.tsx | 5 ++++-
.../CreateTransformForm/CreateTransformForm.tsx | 10 +++++++++-
.../CreateTransformStep2/CreateTransformStep2.tsx | 7 +++++--
3 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index fd21b5ca7..bb44fff01 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -13,7 +13,7 @@
* permissions and limitations under the License.
*/
-import { EuiSpacer, EuiText } from "@elastic/eui";
+import { EuiDataGrid, EuiSpacer, EuiText } from "@elastic/eui";
import React, { Component } from "react";
import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
@@ -66,6 +66,9 @@ export default class DefineTransforms extends Component
{`Viewing sample data from index ${sourceIndex}, filtered by order.type:sales_order, order.success:true`}
+ {/**/}
);
}
diff --git a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
index 950e8db45..f3294d122 100644
--- a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
@@ -330,6 +330,8 @@ export default class CreateTransformForm extends Component
-
+
{
@@ -43,7 +46,7 @@ export default class CreateTransformStep2 extends ComponentDefine transform
-
+
From fedddb4838370a6c20d775bce15029540cde1807 Mon Sep 17 00:00:00 2001
From: Eric Lobdell
Date: Tue, 27 Apr 2021 10:55:36 -0700
Subject: [PATCH 14/93] Initial draft commit for CreateTransform UI
---
.../ConfigureTransform/ConfigureTransform.tsx | 56 +++
.../components/ConfigureTransform/index.ts | 18 +
.../CreateTransformSteps.tsx | 51 +++
.../components/CreateTransformSteps/index.ts | 18 +
.../JobNameAndIndices/JobNameAndIndices.tsx | 93 ++++
.../components/JobNameAndIndices/index.ts | 18 +
.../components/Schedule/Schedule.tsx | 95 ++++
.../components/Schedule/index.ts | 18 +
.../TransformIndices/TransformIndices.tsx | 175 ++++++++
.../components/TransformIndices/index.ts | 18 +
.../CreateTransform/CreateTransform.tsx | 73 ++++
.../containers/CreateTransform/index.ts | 18 +
.../CreateTransformForm.tsx | 411 ++++++++++++++++++
.../containers/CreateTransformForm/index.ts | 18 +
.../CreateTransformStep2.tsx | 65 +++
.../containers/CreateTransformStep2/index.ts | 18 +
.../CreateTransformStep3.tsx | 146 +++++++
.../containers/CreateTransformStep3/index.ts | 18 +
.../CreateTransformStep4.tsx | 84 ++++
.../containers/CreateTransformStep4/index.ts | 18 +
public/pages/CreateTransform/index.ts | 18 +
.../pages/CreateTransform/utils/constants.ts | 88 ++++
public/pages/CreateTransform/utils/helpers.ts | 63 +++
public/pages/Main/Main.tsx | 3 +-
24 files changed, 1600 insertions(+), 1 deletion(-)
create mode 100644 public/pages/CreateTransform/components/ConfigureTransform/ConfigureTransform.tsx
create mode 100644 public/pages/CreateTransform/components/ConfigureTransform/index.ts
create mode 100644 public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
create mode 100644 public/pages/CreateTransform/components/CreateTransformSteps/index.ts
create mode 100644 public/pages/CreateTransform/components/JobNameAndIndices/JobNameAndIndices.tsx
create mode 100644 public/pages/CreateTransform/components/JobNameAndIndices/index.ts
create mode 100644 public/pages/CreateTransform/components/Schedule/Schedule.tsx
create mode 100644 public/pages/CreateTransform/components/Schedule/index.ts
create mode 100644 public/pages/CreateTransform/components/TransformIndices/TransformIndices.tsx
create mode 100644 public/pages/CreateTransform/components/TransformIndices/index.ts
create mode 100644 public/pages/CreateTransform/containers/CreateTransform/CreateTransform.tsx
create mode 100644 public/pages/CreateTransform/containers/CreateTransform/index.ts
create mode 100644 public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
create mode 100644 public/pages/CreateTransform/containers/CreateTransformForm/index.ts
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep2/index.ts
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep3/CreateTransformStep3.tsx
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep3/index.ts
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep4/CreateTransformStep4.tsx
create mode 100644 public/pages/CreateTransform/containers/CreateTransformStep4/index.ts
create mode 100644 public/pages/CreateTransform/index.ts
create mode 100644 public/pages/CreateTransform/utils/constants.ts
create mode 100644 public/pages/CreateTransform/utils/helpers.ts
diff --git a/public/pages/CreateTransform/components/ConfigureTransform/ConfigureTransform.tsx b/public/pages/CreateTransform/components/ConfigureTransform/ConfigureTransform.tsx
new file mode 100644
index 000000000..6a1fa6732
--- /dev/null
+++ b/public/pages/CreateTransform/components/ConfigureTransform/ConfigureTransform.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent } from "react";
+import { EuiSpacer, EuiFormRow, EuiFieldText, EuiTextArea, EuiText, EuiFlexGroup, EuiFlexItem } from "@elastic/eui";
+import { ContentPanel } from "../../../../components/ContentPanel";
+
+interface ConfigureTransformProps {
+ isEdit: boolean;
+ transformId: string;
+ transformIdError: string;
+ onChangeName: (value: ChangeEvent) => void;
+ onChangeDescription: (value: ChangeEvent) => void;
+ description: string;
+}
+
+const ConfigureTransform = ({ isEdit, transformId, transformIdError, onChangeName, onChangeDescription, description }: ConfigureTransformProps) => (
+
+
+
+
+
+
+
+
+
+
+ Description
+
+
+
+
+ - optional
+
+
+
+
+
+
+
+
+
+);
+export default ConfigureTransform;
diff --git a/public/pages/CreateTransform/components/ConfigureTransform/index.ts b/public/pages/CreateTransform/components/ConfigureTransform/index.ts
new file mode 100644
index 000000000..b935263fd
--- /dev/null
+++ b/public/pages/CreateTransform/components/ConfigureTransform/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import ConfigureTransform from "./ConfigureTransform";
+
+export default ConfigureTransform;
diff --git a/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx b/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
new file mode 100644
index 000000000..02bdc136b
--- /dev/null
+++ b/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+import React from "react";
+import { EuiSteps } from "@elastic/eui";
+
+interface CreateTransformStepsProps {
+ step: number;
+}
+
+const setOfSteps = (step: number) => {
+ return [
+ {
+ title: "Set up indices",
+ children: null,
+ },
+ {
+ title: "Define aggregations and metrics",
+ children: null,
+ status: step < 2 ? "disabled" : null,
+ },
+ {
+ title: "Specify schedule",
+ children: null,
+ status: step < 3 ? "disabled" : null,
+ },
+ {
+ title: "Review and create",
+ children: null,
+ status: step < 4 ? "disabled" : null,
+ },
+ ];
+};
+const CreateTransformSteps = ({ step }: CreateTransformStepsProps) => (
+
+
+
+);
+
+export default CreateTransformSteps;
diff --git a/public/pages/CreateTransform/components/CreateTransformSteps/index.ts b/public/pages/CreateTransform/components/CreateTransformSteps/index.ts
new file mode 100644
index 000000000..fe481f23a
--- /dev/null
+++ b/public/pages/CreateTransform/components/CreateTransformSteps/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransformSteps from "./CreateTransformSteps";
+
+export default CreateTransformSteps;
diff --git a/public/pages/CreateTransform/components/JobNameAndIndices/JobNameAndIndices.tsx b/public/pages/CreateTransform/components/JobNameAndIndices/JobNameAndIndices.tsx
new file mode 100644
index 000000000..e2e01d3dd
--- /dev/null
+++ b/public/pages/CreateTransform/components/JobNameAndIndices/JobNameAndIndices.tsx
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component } from "react";
+import { EuiFlexGrid, EuiSpacer, EuiFlexItem, EuiText } from "@elastic/eui";
+import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
+import { ModalConsumer } from "../../../../components/Modal";
+import { IndexItem } from "../../../../../models/interfaces";
+
+interface JobNameAndIndicesProps {
+ transformId: string;
+ description: string;
+ sourceIndex: { label: string; value?: IndexItem }[];
+ targetIndex: { label: string; value?: IndexItem }[];
+ onChangeStep: (step: number) => void;
+}
+
+export default class JobNameAndIndices extends Component {
+ constructor(props: JobNameAndIndicesProps) {
+ super(props);
+ }
+
+ render() {
+ const { transformId, description, onChangeStep, sourceIndex, targetIndex } = this.props;
+
+ return (
+
+ {() => (
+ onChangeStep(1),
+ },
+ },
+ ]}
+ />
+ )}
+
+ }
+ bodyStyles={{ padding: "initial" }}
+ title="Job name and indices"
+ titleSize="m"
+ >
+
+
+
+
+
+ Name
+ {transformId}
+
+
+
+
+ Source Index
+ {sourceIndex[0].label}
+
+
+
+
+ Target index
+ {targetIndex[0].label}
+
+
+
+
+ Description
+ {description == "" ? "-" : description}
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/components/JobNameAndIndices/index.ts b/public/pages/CreateTransform/components/JobNameAndIndices/index.ts
new file mode 100644
index 000000000..aa4b17446
--- /dev/null
+++ b/public/pages/CreateTransform/components/JobNameAndIndices/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import JobNameAndIndices from "./JobNameAndIndices";
+
+export default JobNameAndIndices;
diff --git a/public/pages/CreateTransform/components/Schedule/Schedule.tsx b/public/pages/CreateTransform/components/Schedule/Schedule.tsx
new file mode 100644
index 000000000..9a8e45021
--- /dev/null
+++ b/public/pages/CreateTransform/components/Schedule/Schedule.tsx
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component } from "react";
+import moment from "moment-timezone";
+import {
+ EuiSpacer,
+ EuiCheckbox,
+ EuiRadioGroup,
+ EuiFormRow,
+ EuiSelect,
+ EuiFieldNumber,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiTextArea,
+ EuiFormHelpText,
+ EuiText,
+} from "@elastic/eui";
+import { DelayTimeunitOptions, ScheduleIntervalTimeunitOptions } from "../../utils/constants";
+import { ContentPanel } from "../../../../components/ContentPanel";
+
+interface ScheduleProps {
+ isEdit: boolean;
+ transformId: string;
+ transformIdError: string;
+ jobEnabledByDefault: boolean;
+ pageSize: number;
+ onChangeJobEnabledByDefault: () => void;
+ onChangePage: (e: ChangeEvent) => void;
+}
+
+const radios = [
+ {
+ id: "no",
+ label: "No",
+ },
+ {
+ id: "yes",
+ label: "Yes",
+ },
+];
+
+const timezones = moment.tz.names().map((tz) => ({ label: tz, text: tz }));
+
+export default class Schedule extends Component {
+ constructor(props: ScheduleProps) {
+ super(props);
+ }
+
+ render() {
+ const {
+ isEdit,
+ jobEnabledByDefault,
+ pageSize,
+ onChangeJobEnabledByDefault,
+ onChangePage,
+ } = this.props;
+ return (
+
+
+ {!isEdit && (
+
+ )}
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/components/Schedule/index.ts b/public/pages/CreateTransform/components/Schedule/index.ts
new file mode 100644
index 000000000..f18d83cb2
--- /dev/null
+++ b/public/pages/CreateTransform/components/Schedule/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import Schedule from "./Schedule";
+
+export default Schedule;
diff --git a/public/pages/CreateTransform/components/TransformIndices/TransformIndices.tsx b/public/pages/CreateTransform/components/TransformIndices/TransformIndices.tsx
new file mode 100644
index 000000000..2b0abb1f0
--- /dev/null
+++ b/public/pages/CreateTransform/components/TransformIndices/TransformIndices.tsx
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component, Fragment } from "react";
+import { EuiSpacer, EuiFormRow, EuiComboBox, EuiCallOut } from "@elastic/eui";
+import { ContentPanel } from "../../../../components/ContentPanel";
+import { EuiComboBoxOptionOption } from "@elastic/eui/src/components/combo_box/types";
+import { IndexItem } from "../../../../../models/interfaces";
+import IndexService from "../../../../services/IndexService";
+import _ from "lodash";
+import { CoreServicesContext } from "../../../../components/core_services";
+
+interface TransformIndicesProps {
+ indexService: IndexService;
+ sourceIndex: { label: string; value?: IndexItem }[];
+ sourceIndexError: string;
+ targetIndex: { label: string; value?: IndexItem }[];
+ targetIndexError: string;
+ onChangeSourceIndex: (options: EuiComboBoxOptionOption[]) => void;
+ onChangeTargetIndex: (options: EuiComboBoxOptionOption[]) => void;
+ hasAggregation: boolean;
+}
+
+interface TransformIndicesState {
+ isLoading: boolean;
+ indexOptions: { label: string; value?: IndexItem }[];
+ targetIndexOptions: { label: string; value?: IndexItem }[];
+}
+
+export default class TransformIndices extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: TransformIndicesProps) {
+ super(props);
+ this.state = {
+ isLoading: true,
+ indexOptions: [],
+ targetIndexOptions: [],
+ };
+
+ this.onIndexSearchChange = _.debounce(this.onIndexSearchChange, 500, { leading: true });
+ }
+
+ async componentDidMount(): Promise {
+ await this.onIndexSearchChange("");
+ }
+
+ onIndexSearchChange = async (searchValue: string): Promise => {
+ const { indexService } = this.props;
+ this.setState({ isLoading: true, indexOptions: [] });
+ try {
+ const queryObject = { from: 0, size: 10, search: searchValue, sortDirection: "desc", sortField: "index" };
+ const getIndicesResponse = await indexService.getIndices(queryObject);
+ if (getIndicesResponse.ok) {
+ const options = searchValue.trim() ? [{ label: `${searchValue}*` }] : [];
+ const indices = getIndicesResponse.response.indices.map((index: IndexItem) => ({
+ label: index.index,
+ }));
+ this.setState({ indexOptions: options.concat(indices), targetIndexOptions: indices });
+ } else {
+ if (getIndicesResponse.error.startsWith("[index_not_found_exception]")) {
+ this.context.notifications.toasts.addDanger("No index available");
+ } else {
+ this.context.notifications.toasts.addDanger(getIndicesResponse.error);
+ }
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(err.message);
+ }
+
+ this.setState({ isLoading: false });
+ };
+
+ onCreateOption = (searchValue: string, flattenedOptions: { label: string; value?: IndexItem }[]): void => {
+ const { targetIndexOptions } = this.state;
+ const { onChangeTargetIndex } = this.props;
+ const normalizedSearchValue = searchValue.trim();
+
+ if (!normalizedSearchValue) {
+ return;
+ }
+
+ const newOption = {
+ label: searchValue,
+ };
+
+ // Create the option if it doesn't exist.
+ if (flattenedOptions.findIndex((option) => option.label.trim() === normalizedSearchValue) === -1) {
+ targetIndexOptions.concat(newOption);
+ this.setState({ targetIndexOptions: targetIndexOptions });
+ }
+ onChangeTargetIndex([newOption]);
+ };
+
+ render() {
+ const {
+ sourceIndex,
+ sourceIndexError,
+ targetIndex,
+ targetIndexError,
+ onChangeSourceIndex,
+ onChangeTargetIndex,
+ hasAggregation,
+ } = this.props;
+ const { isLoading, indexOptions, targetIndexOptions } = this.state;
+ return (
+
+
+
+
+ You can't change indices after creating a job. Double-check the source and target index names before proceeding.
+
+ {hasAggregation && (
+
+
+
+ Note: changing source index will erase all existing definitions about aggregations and metrics.
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/components/TransformIndices/index.ts b/public/pages/CreateTransform/components/TransformIndices/index.ts
new file mode 100644
index 000000000..ffee9e56f
--- /dev/null
+++ b/public/pages/CreateTransform/components/TransformIndices/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import TransformIndices from "./TransformIndices";
+
+export default TransformIndices;
diff --git a/public/pages/CreateTransform/containers/CreateTransform/CreateTransform.tsx b/public/pages/CreateTransform/containers/CreateTransform/CreateTransform.tsx
new file mode 100644
index 000000000..05b223724
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransform/CreateTransform.tsx
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component } from "react";
+import { EuiSpacer, EuiTitle, EuiFlexGroup, EuiFlexItem, EuiComboBoxOptionOption } from "@elastic/eui";
+import { RouteComponentProps } from "react-router-dom";
+import { TransformService } from "../../../../services";
+import ConfigureTransform from "../../components/ConfigureTransform";
+import TransformIndices from "../../components/TransformIndices";
+import CreateTransformSteps from "../../components/CreateTransformSteps";
+import IndexService from "../../../../services/IndexService";
+import { IndexItem } from "../../../../../models/interfaces";
+
+interface CreateTransformProps extends RouteComponentProps {
+ transformService: TransformService;
+ indexService: IndexService;
+ transformId: string;
+ transformIdError: string;
+ submitError: string;
+ isSubmitting: boolean;
+ hasSubmitted: boolean;
+ description: string;
+ sourceIndex: { label: string; value?: IndexItem }[];
+ sourceIndexError: string;
+ targetIndex: { label: string; value?: IndexItem }[];
+ targetIndexError: string;
+ onChangeName: (e: ChangeEvent) => void;
+ onChangeDescription: (value: ChangeEvent) => void;
+ onChangeSourceIndex: (options: EuiComboBoxOptionOption[]) => void;
+ onChangeTargetIndex: (options: EuiComboBoxOptionOption[]) => void;
+ currentStep: number;
+ hasAggregation: boolean;
+}
+
+export default class CreateTransform extends Component {
+ render() {
+ if (this.props.currentStep !== 1) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+ Set up indices
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/containers/CreateTransform/index.ts b/public/pages/CreateTransform/containers/CreateTransform/index.ts
new file mode 100644
index 000000000..b01f74a8a
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransform/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransform from "./CreateTransform";
+
+export default CreateTransform;
diff --git a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
new file mode 100644
index 000000000..16c135c2c
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
@@ -0,0 +1,411 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component } from "react";
+import { EuiButton, EuiButtonEmpty, EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem } from "@elastic/eui";
+import { RouteComponentProps } from "react-router-dom";
+import moment from "moment";
+import { TransformService } from "../../../../services";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import IndexService from "../../../../services/IndexService";
+import { ManagedCatIndex } from "../../../../../server/models/interfaces";
+import CreateTransform from "../CreateTransform";
+import CreateTransformStep2 from "../CreateTransformStep2";
+import { DimensionItem, FieldItem, IndexItem, MetricItem, Transform } from "../../../../../models/interfaces";
+import { getErrorMessage } from "../../../../utils/helpers";
+import { EMPTY_TRANSFORM } from "../../utils/constants";
+import CreateTransformStep3 from "../CreateTransformStep3";
+import CreateTransformStep4 from "../CreateTransformStep4";
+import { compareFieldItem, parseFieldOptions } from "../../utils/helpers";
+import { CoreServicesContext } from "../../../../components/core_services";
+
+interface CreateTransformFormProps extends RouteComponentProps {
+ transformService: TransformService;
+ indexService: IndexService;
+}
+
+interface CreateTransformFormState {
+ currentStep: number;
+ transformId: string;
+ transformIdError: string;
+ transformSeqNo: number | null;
+ transformPrimaryTerm: number | null;
+ submitError: string;
+ isSubmitting: boolean;
+ hasSubmitted: boolean;
+ loadingIndices: boolean;
+ indices: ManagedCatIndex[];
+ totalIndices: number;
+
+ description: string;
+ sourceIndex: { label: string; value?: IndexItem }[];
+ sourceIndexError: string;
+ targetIndex: { label: string; value?: IndexItem }[];
+ targetIndexError: string;
+
+ mappings: any;
+ allMappings: FieldItem[][];
+ fields: FieldItem[];
+ selectedTerms: FieldItem[];
+ selectedDimensionField: DimensionItem[];
+ selectedMetrics: MetricItem[];
+ metricError: string;
+ timestamp: EuiComboBoxOptionOption[];
+ timestampError: string;
+ intervalType: string;
+ intervalValue: number;
+ timezone: string;
+ timeunit: string;
+ selectedFields: FieldItem[];
+ jobEnabledByDefault: boolean;
+
+ continuousJob: string;
+ continuousDefinition: string;
+ interval: number;
+ intervalError: string;
+ intervalTimeunit: string;
+ cronExpression: string;
+ cronTimezone: string;
+ pageSize: number;
+ delayTime: number | undefined;
+ delayTimeunit: string;
+ transformJSON: any;
+}
+
+export default class CreateTransformForm extends Component {
+ static contextType = CoreServicesContext;
+
+ constructor(props: CreateTransformFormProps) {
+ super(props);
+
+ this.state = {
+ currentStep: 1,
+ transformSeqNo: null,
+ transformPrimaryTerm: null,
+ transformId: "",
+ transformIdError: "",
+ submitError: "",
+ isSubmitting: false,
+ hasSubmitted: false,
+ loadingIndices: true,
+ indices: [],
+ totalIndices: 0,
+
+ mappings: "",
+ allMappings: [],
+ fields: [],
+ description: "",
+
+ sourceIndex: [],
+ sourceIndexError: "",
+ targetIndex: [],
+ targetIndexError: "",
+
+ jobEnabledByDefault: true,
+ pageSize: 1000,
+ transformJSON: JSON.parse(EMPTY_TRANSFORM),
+ };
+ this._next = this._next.bind(this);
+ this._prev = this._prev.bind(this);
+ }
+
+ componentDidMount = async (): Promise => {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS, BREADCRUMBS.CREATE_TRANSFORM]);
+ };
+
+ getMappings = async (srcIndex: string): Promise => {
+ if (!srcIndex.length) return;
+ try {
+ const { transformService } = this.props;
+ const response = await transformService.getMappings(srcIndex);
+ if (response.ok) {
+ let allMappings: FieldItem[][] = [];
+ const mappings = response.response;
+ //Push mappings array to allMappings 2D array first
+ for (let index in mappings) {
+ allMappings.push(parseFieldOptions("", mappings[index].mappings.properties));
+ }
+ //Find intersect from all mappings
+ const fields = allMappings.reduce((mappingA, mappingB) =>
+ mappingA.filter((itemA) => mappingB.some((itemB) => compareFieldItem(itemA, itemB)))
+ );
+ this.setState({ mappings, fields, allMappings });
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not load fields: ${response.error}`);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, "Could not load fields"));
+ }
+ };
+
+ _next() {
+ let currentStep = this.state.currentStep;
+ let error = false;
+ //Verification here
+ if (currentStep == 1) {
+ const { transformId, sourceIndex, targetIndex } = this.state;
+
+ if (!transformId) {
+ this.setState({ submitError: "Job name is required.", transformIdError: "Job name is required." });
+ error = true;
+ }
+ if (sourceIndex.length == 0) {
+ this.setState({ submitError: "Source index is required.", sourceIndexError: "Source index is required." });
+ error = true;
+ }
+ if (targetIndex.length == 0) {
+ this.setState({ submitError: "Target index is required.", targetIndexError: "Target index is required." });
+ error = true;
+ }
+ } else if (currentStep == 2) {
+ } else if (currentStep == 3) {
+ //Check if interval is a valid value and is specified.
+ const { intervalError, continuousDefinition } = this.state;
+ if (continuousDefinition == "fixed") {
+ if (intervalError != "") {
+ const intervalErrorMsg = "Interval value is required.";
+ this.setState({ submitError: intervalErrorMsg, intervalError: intervalErrorMsg });
+ error = true;
+ }
+ }
+ }
+
+ if (error) return;
+
+ currentStep = currentStep >= 3 ? 4 : currentStep + 1;
+
+ this.setState({
+ submitError: "",
+ currentStep: currentStep,
+ });
+ }
+
+ _prev() {
+ let currentStep = this.state.currentStep ;
+ // If the current step is 2 or 3, then subtract one on "previous" button click
+ currentStep = currentStep <= 1 ? 1 : currentStep - 1;
+ this.setState({
+ currentStep: currentStep,
+ });
+ }
+
+ onChangeStep = (step: number): void => {
+ if (step > 3) return;
+ this.setState({
+ currentStep: step,
+ });
+ };
+
+ onChangeDescription = (e: ChangeEvent): void => {
+ const description = e.target.value;
+ let newJSON = this.state.transformJSON;
+ newJSON.transform.description = description;
+ this.setState({ description: description, transformJSON: newJSON });
+ };
+
+ onChangeName = (e: ChangeEvent): void => {
+ const transformId = e.target.value;
+ this.setState({ transformId, transformIdError: transformId ? "" : "Name is required" });
+ };
+
+ onChangeSourceIndex = async (options: EuiComboBoxOptionOption[]): Promise => {
+ let newJSON = this.state.transformJSON;
+ let sourceIndex = options.map(function (option) {
+ return option.label;
+ });
+ const sourceIndexError = sourceIndex.length ? "" : "Source index is required";
+ const srcIndexText = sourceIndex.length ? sourceIndex[0] : "";
+ newJSON.transform.source_index = srcIndexText;
+ this.setState({ sourceIndex: options, transformJSON: newJSON, sourceIndexError: sourceIndexError });
+ this.setState({
+ selectedDimensionField: [],
+ selectedMetrics: [],
+ });
+ await this.getMappings(srcIndexText);
+ };
+
+ onChangeTargetIndex = (options: EuiComboBoxOptionOption[]): void => {
+ //Try to get label text from option from the only array element in options, if exists
+ let newJSON = this.state.transformJSON;
+ let targetIndex = options.map(function (option) {
+ return option.label;
+ });
+
+ const targetIndexError = targetIndex.length ? "" : "Target index is required";
+
+ newJSON.transform.target_index = targetIndex[0];
+ this.setState({ targetIndex: options, transformJSON: newJSON, targetIndexError: targetIndexError });
+ };
+
+ setDateHistogram = (): void => {
+ const { intervalType, intervalValue, timeunit } = this.state;
+ let newJSON = this.state.transformJSON;
+ if (intervalType == "calendar") {
+ newJSON.transform.dimensions[0].date_histogram.calendar_interval = `1${timeunit}`;
+ delete newJSON.transform.dimensions[0].date_histogram["fixed_interval"];
+ } else {
+ newJSON.transform.dimensions[0].date_histogram.fixed_interval = `${intervalValue}${timeunit}`;
+ delete newJSON.transform.dimensions[0].date_histogram["calendar_interval"];
+ }
+ this.setState({ transformJSON: newJSON });
+ };
+
+ onChangeJobEnabledByDefault = (): void => {
+ const checked = this.state.jobEnabledByDefault;
+ let newJSON = this.state.transformJSON;
+ newJSON.transform.enabled = !checked;
+ this.setState({ jobEnabledByDefault: !checked, transformJSON: newJSON });
+ };
+
+ onChangePage = (e: ChangeEvent): void => {
+ let newJSON = this.state.transformJSON;
+ newJSON.transform.page_size = e.target.valueAsNumber;
+ this.setState({ pageSize: e.target.valueAsNumber, transformJSON: newJSON });
+ };
+
+ onSubmit = async (): Promise => {
+ const { transformId, transformJSON } = this.state;
+ this.setState({ submitError: "", isSubmitting: true, hasSubmitted: true });
+ try {
+ if (!transformId) {
+ this.setState({ transformIdError: "Required" });
+ } else {
+ this.setDateHistogram();
+ await this.onCreate(transformId, transformJSON);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger("Invalid Transform JSON");
+ console.error(err);
+ }
+
+ this.setState({ isSubmitting: false });
+ };
+
+ onCancel = (): void => {
+ this.props.history.push(ROUTES.TRANSFORMS);
+ };
+
+ onCreate = async (transformId: string, transform: Transform): Promise => {
+ const { transformService } = this.props;
+ try {
+ const response = await transformService.putTransform(transform, transformId);
+ if (response.ok) {
+ this.context.notifications.toasts.addSuccess(`Created transform: ${response.response._id}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ } else {
+ this.setState({ submitError: response.error });
+ this.context.notifications.toasts.addDanger(`Failed to create transform: ${response.error}`);
+ }
+ } catch (err) {
+ this.setState({ submitError: getErrorMessage(err, "There was a problem creating the transform job") });
+ this.context.notifications.toasts.addDanger(
+ `Failed to create transform: ${getErrorMessage(err, "There was a problem creating the transform job")}`
+ );
+ }
+ };
+
+ render() {
+ const {
+ transformId,
+ transformIdError,
+ submitError,
+ isSubmitting,
+ hasSubmitted,
+ description,
+ sourceIndex,
+ sourceIndexError,
+ targetIndex,
+ targetIndexError,
+ currentStep,
+
+ jobEnabledByDefault,
+ pageSize,
+ } = this.state;
+ return (
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/containers/CreateTransformForm/index.ts b/public/pages/CreateTransform/containers/CreateTransformForm/index.ts
new file mode 100644
index 000000000..d933dd68a
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformForm/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransformForm from "./CreateTransformForm";
+
+export default CreateTransformForm;
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
new file mode 100644
index 000000000..9ad07d434
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component } from "react";
+import { EuiSpacer, EuiTitle, EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiComboBoxOptionOption } from "@elastic/eui";
+import { RouteComponentProps } from "react-router-dom";
+import { TransformService } from "../../../../services";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import CreateTransformSteps from "../../components/CreateTransformSteps";
+import { DimensionItem, FieldItem, MetricItem } from "../../../../../models/interfaces";
+import { CoreServicesContext } from "../../../../components/core_services";
+
+interface CreateTransformStep2Props extends RouteComponentProps {
+ transformService: TransformService;
+ currentStep: number;
+}
+
+export default class CreateTransformStep2 extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: CreateTransformStep2Props) {
+ super(props);
+ }
+
+ componentDidMount = async (): Promise => {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS]);
+ };
+
+ onCancel = (): void => {
+ this.props.history.push(ROUTES.TRANSFORMS);
+ };
+
+ render() {
+ if (this.props.currentStep !== 2) return null;
+ const { fields, timestamp } = this.props;
+
+ return (
+
+
+
+
+
+
+
+ Definition Placeholder
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep2/index.ts b/public/pages/CreateTransform/containers/CreateTransformStep2/index.ts
new file mode 100644
index 000000000..1b7f6dbd7
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep2/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransformStep2 from "./CreateTransformStep2";
+
+export default CreateTransformStep2;
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep3/CreateTransformStep3.tsx b/public/pages/CreateTransform/containers/CreateTransformStep3/CreateTransformStep3.tsx
new file mode 100644
index 000000000..c359130ef
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep3/CreateTransformStep3.tsx
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component } from "react";
+import { EuiSpacer, EuiTitle, EuiFlexGroup, EuiFlexItem } from "@elastic/eui";
+import { RouteComponentProps } from "react-router-dom";
+import { TransformService } from "../../../../services";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import { getErrorMessage } from "../../../../utils/helpers";
+import { Transform } from "../../../../../models/interfaces";
+import CreateTransformSteps from "../../components/CreateTransformSteps";
+import Schedule from "../../components/Schedule";
+import { CoreServicesContext } from "../../../../components/core_services";
+
+interface CreateTransformProps extends RouteComponentProps {
+ transformService: TransformService;
+ currentStep: number;
+ jobEnabledByDefault: boolean;
+ pageSize: number;
+ onChangeJobEnabledByDefault: () => void;
+ onChangePage: (e: ChangeEvent) => void;
+}
+
+interface CreateTransformState {
+ transformId: string;
+ transformIdError: string;
+ transformSeqNo: number | null;
+ transformPrimaryTerm: number | null;
+ submitError: string;
+ isSubmitting: boolean;
+ hasSubmitted: boolean;
+}
+
+export default class CreateTransformStep3 extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: CreateTransformProps) {
+ super(props);
+
+ this.state = {
+ transformSeqNo: null,
+ transformPrimaryTerm: null,
+ transformId: "",
+ transformIdError: "",
+ submitError: "",
+ isSubmitting: false,
+ hasSubmitted: false,
+ };
+ }
+
+ componentDidMount = async (): Promise => {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS]);
+ };
+
+ onCreate = async (transformId: string, transform: Transform): Promise => {
+ const { transformService } = this.props;
+ try {
+ const response = await transformService.putTransform(transform, transformId);
+ if (response.ok) {
+ this.context.notifications.toasts.addSuccess(`Created transform: ${response.response._id}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ } else {
+ this.setState({ submitError: response.error });
+ }
+ } catch (err) {
+ this.setState({ submitError: getErrorMessage(err, "There was a problem creating the transform") });
+ }
+ };
+
+ onUpdate = async (transformId: string, transform: Transform): Promise => {
+ try {
+ const { transformService } = this.props;
+ const { transformPrimaryTerm, transformSeqNo } = this.state;
+ if (transformSeqNo == null || transformPrimaryTerm == null) {
+ this.context.notifications.toasts.addDanger("Could not update transform without seqNo and primaryTerm");
+ return;
+ }
+ const response = await transformService.putTransform(transform, transformId, transformSeqNo, transformPrimaryTerm);
+ if (response.ok) {
+ this.context.notifications.toasts.addSuccess(`Updated transform: ${response.response._id}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ } else {
+ this.setState({ submitError: response.error });
+ }
+ } catch (err) {
+ this.setState({ submitError: getErrorMessage(err, "There was a problem updating the transform") });
+ }
+ };
+
+ onCancel = (): void => {
+ this.props.history.push(ROUTES.TRANSFORMS);
+ };
+
+ onChange = (e: ChangeEvent): void => {
+ const { hasSubmitted } = this.state;
+ const transformId = e.target.value;
+ if (hasSubmitted) this.setState({ transformId, transformIdError: transformId ? "" : "Required" });
+ else this.setState({ transformId });
+ };
+
+ render() {
+ if (this.props.currentStep != 3) return null;
+ const {
+ jobEnabledByDefault,
+ pageSize,
+ onChangePage,
+ } = this.props;
+ const { transformId, transformIdError } = this.state;
+ return (
+
+
+
+
+
+
+
+ Specify schedule
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep3/index.ts b/public/pages/CreateTransform/containers/CreateTransformStep3/index.ts
new file mode 100644
index 000000000..c2626fe72
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep3/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransformStep3 from "./CreateTransformStep3";
+
+export default CreateTransformStep3;
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep4/CreateTransformStep4.tsx b/public/pages/CreateTransform/containers/CreateTransformStep4/CreateTransformStep4.tsx
new file mode 100644
index 000000000..328533609
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep4/CreateTransformStep4.tsx
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component } from "react";
+import { EuiSpacer, EuiTitle, EuiFlexGroup, EuiFlexItem, EuiComboBoxOptionOption, EuiCallOut } from "@elastic/eui";
+import { RouteComponentProps } from "react-router-dom";
+import { TransformService } from "../../../../services";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import { DimensionItem, IndexItem, MetricItem } from "../../../../../models/interfaces";
+import CreateTransformSteps from "../../components/CreateTransformSteps";
+import JobNameAndIndices from "../../components/JobNameAndIndices";
+import { CoreServicesContext } from "../../../../components/core_services";
+
+interface CreateTransformProps extends RouteComponentProps {
+ transformService: TransformService;
+ submitError: string;
+ currentStep: number;
+ onChangeStep: (step: number) => void;
+ transformId: string;
+ description: string;
+ sourceIndex: { label: string; value?: IndexItem }[];
+ targetIndex: { label: string; value?: IndexItem }[];
+
+ timestamp: EuiComboBoxOptionOption[];
+ timezone: string;
+ timeunit: string;
+
+ jobEnabledByDefault: boolean;
+ pageSize: number;
+}
+
+export default class CreateTransformStep4 extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: CreateTransformProps) {
+ super(props);
+ }
+
+ componentDidMount = async (): Promise => {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS]);
+ };
+
+ onCancel = (): void => {
+ this.props.history.push(ROUTES.TRANSFORMS);
+ };
+
+ render() {
+ if (this.props.currentStep != 4) return null;
+
+ return (
+
+
+
+
+
+
+
+ Review and create
+
+
+
+
+
+
+ You can't change aggregations or metrics after creating a job. Double-check your choices before proceeding.
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep4/index.ts b/public/pages/CreateTransform/containers/CreateTransformStep4/index.ts
new file mode 100644
index 000000000..379985a8a
--- /dev/null
+++ b/public/pages/CreateTransform/containers/CreateTransformStep4/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransformStep4 from "./CreateTransformStep4";
+
+export default CreateTransformStep4;
diff --git a/public/pages/CreateTransform/index.ts b/public/pages/CreateTransform/index.ts
new file mode 100644
index 000000000..2ca67cce6
--- /dev/null
+++ b/public/pages/CreateTransform/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import CreateTransform from "./containers/CreateTransform";
+
+export default CreateTransform;
diff --git a/public/pages/CreateTransform/utils/constants.ts b/public/pages/CreateTransform/utils/constants.ts
new file mode 100644
index 000000000..008f1bf34
--- /dev/null
+++ b/public/pages/CreateTransform/utils/constants.ts
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+export const EMPTY_TRANSFORM = JSON.stringify({
+ transform: {
+ continuous: false,
+ description: "",
+ dimensions: [
+ {
+ date_histogram: {
+ source_field: "",
+ fixed_interval: "1h",
+ timezone: "UTC",
+ },
+ },
+ ],
+ enabled: true,
+ metrics: [],
+ page_size: 1000,
+ roles: [],
+ schedule: {
+ interval: {
+ start_time: 234802,
+ period: 1,
+ unit: "MINUTES",
+ },
+ },
+ source_index: "",
+ target_index: "",
+ },
+});
+
+export const FixedTimeunitOptions = [
+ { value: "ms", text: "Millisecond(s)" },
+ { value: "s", text: "Second(s)" },
+ { value: "m", text: "Minute(s)" },
+ { value: "h", text: "Hour(s)" },
+ { value: "d", text: "Day(s)" },
+];
+
+export const DelayTimeunitOptions = [
+ { value: "SECONDS", text: "Second(s)" },
+ { value: "MINUTES", text: "Minute(s)" },
+ { value: "HOURS", text: "Hour(s)" },
+ { value: "DAYS", text: "Day(s)" },
+];
+
+export const CalendarTimeunitOptions = [
+ { value: "m", text: "Minute" },
+ { value: "h", text: "Hour" },
+ { value: "d", text: "Day" },
+ { value: "w", text: "Week" },
+ { value: "M", text: "Month" },
+ { value: "q", text: "Quarter" },
+ { value: "y", text: "Year" },
+];
+
+export const ScheduleIntervalTimeunitOptions = [
+ { value: "MINUTES", text: "Minute(s)" },
+ { value: "HOURS", text: "Hour(s)" },
+ { value: "DAYS", text: "Day(s)" },
+];
+
+export const AddFieldsColumns = [
+ {
+ field: "label",
+ name: "Field name",
+ sortable: true,
+ },
+ {
+ field: "type",
+ name: "Field type",
+ sortable: true,
+ render: (type: string | undefined) => (type == null || type == undefined ? "-" : type),
+ },
+];
diff --git a/public/pages/CreateTransform/utils/helpers.ts b/public/pages/CreateTransform/utils/helpers.ts
new file mode 100644
index 000000000..8e66b67fc
--- /dev/null
+++ b/public/pages/CreateTransform/utils/helpers.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import { FieldItem } from "../../../../models/interfaces";
+
+export const parseTimeunit = (timeunit: string): string => {
+ if (timeunit == "ms" || timeunit == "Milliseconds") return "millisecond(s)";
+ else if (timeunit == "SECONDS" || timeunit == "s" || timeunit == "Seconds") return "second(s)";
+ else if (timeunit == "MINUTES" || timeunit == "m" || timeunit == "Minutes") return "minute(s)";
+ else if (timeunit == "HOURS" || timeunit == "h" || timeunit == "Hours") return "hour(s)";
+ else if (timeunit == "DAYS" || timeunit == "d" || timeunit == "Days") return "day(s)";
+ else if (timeunit == "w") return "week";
+ else if (timeunit == "M") return "month";
+ else if (timeunit == "q") return "quarter";
+ else if (timeunit == "y") return "year";
+
+ return timeunit;
+};
+
+//Returns true if field type is numeric
+export const isNumericMapping = (fieldType: string | undefined): boolean => {
+ return (
+ fieldType == "long" ||
+ fieldType == "integer" ||
+ fieldType == "short" ||
+ fieldType == "byte" ||
+ fieldType == "double" ||
+ fieldType == "float" ||
+ fieldType == "half_float" ||
+ fieldType == "scaled_float"
+ );
+};
+
+export const compareFieldItem = (itemA: FieldItem, itemB: FieldItem): boolean => {
+ return itemB.label == itemA.label && itemA.type == itemB.type;
+};
+
+export const parseFieldOptions = (prefix: string, mappings: any): FieldItem[] => {
+ let fieldsOption: FieldItem[] = [];
+ for (let field in mappings) {
+ if (mappings.hasOwnProperty(field)) {
+ if (mappings[field].type != "object" && mappings[field].type != null && mappings[field].type != "nested")
+ fieldsOption.push({ label: prefix + field, type: mappings[field].type });
+ if (mappings[field].fields != null)
+ fieldsOption = fieldsOption.concat(parseFieldOptions(prefix + field + ".", mappings[field].fields));
+ if (mappings[field].properties != null)
+ fieldsOption = fieldsOption.concat(parseFieldOptions(prefix + field + ".", mappings[field].properties));
+ }
+ }
+ return fieldsOption;
+};
diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx
index c48320e83..8c6fde960 100644
--- a/public/pages/Main/Main.tsx
+++ b/public/pages/Main/Main.tsx
@@ -30,6 +30,7 @@ import { BrowserServices } from "../../models/interfaces";
import { ROUTES } from "../../utils/constants";
import { CoreServicesConsumer } from "../../components/core_services";
import CreateRollupForm from "../CreateRollup/containers/CreateRollupForm";
+import CreateTransformForm from "../CreateTransform/containers/CreateTransformForm";
import EditRollup from "../EditRollup/containers";
import RollupDetails from "../RollupDetails/containers/RollupDetails";
import { EditTransform, Transforms } from "../Transforms";
@@ -210,7 +211,7 @@ export default class Main extends Component {
path={ROUTES.CREATE_TRANSFORM}
render={(props: RouteComponentProps) => (
-
+
)}
/>
From 3820a54cb7352bdd9a8414f56e6c85336f57224d Mon Sep 17 00:00:00 2001
From: Annie
Date: Wed, 28 Apr 2021 18:00:32 -0700
Subject: [PATCH 15/93] Add dependency to rollupService to get mappings
---
.../DefineTransforms/DefineTransforms.tsx | 4 ++--
.../CreateTransformForm/CreateTransformForm.tsx | 8 ++++----
.../CreateTransformStep2/CreateTransformStep2.tsx | 2 ++
public/pages/Main/Main.tsx | 13 +++++++++----
4 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index bb44fff01..0373f0701 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -27,7 +27,7 @@ interface DefineTransformsState {}
export default class DefineTransforms extends Component {
constructor(props: DefineTransformsProps) {
super(props);
- const { transfromId, sourceIndex } = this.props;
+ const { transfromId } = this.props;
this.state = {};
}
@@ -67,7 +67,7 @@ export default class DefineTransforms extends Component{`Viewing sample data from index ${sourceIndex}, filtered by order.type:sales_order, order.success:true`}
{/**/}
);
diff --git a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
index f3294d122..4b182c00e 100644
--- a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
@@ -16,8 +16,7 @@
import React, { ChangeEvent, Component } from "react";
import { EuiButton, EuiButtonEmpty, EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem } from "@elastic/eui";
import { RouteComponentProps } from "react-router-dom";
-import moment from "moment";
-import { TransformService } from "../../../../services";
+import { RollupService, TransformService } from "../../../../services";
import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
import IndexService from "../../../../services/IndexService";
import { ManagedCatIndex } from "../../../../../server/models/interfaces";
@@ -32,6 +31,7 @@ import { compareFieldItem, parseFieldOptions } from "../../utils/helpers";
import { CoreServicesContext } from "../../../../components/core_services";
interface CreateTransformFormProps extends RouteComponentProps {
+ rollupService: RollupService;
transformService: TransformService;
indexService: IndexService;
}
@@ -128,8 +128,8 @@ export default class CreateTransformForm extends Component => {
if (!srcIndex.length) return;
try {
- const { transformService } = this.props;
- const response = await transformService.getMappings(srcIndex);
+ const { rollupService } = this.props;
+ const response = await rollupService.getMappings(srcIndex);
if (response.ok) {
let allMappings: FieldItem[][] = [];
const mappings = response.response;
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
index d616e13ea..e0bdb1196 100644
--- a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
@@ -48,6 +48,8 @@ export default class CreateTransformStep2 extends Component
diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx
index 4f389e157..c093a4082 100644
--- a/public/pages/Main/Main.tsx
+++ b/public/pages/Main/Main.tsx
@@ -93,7 +93,7 @@ export default class Main extends Component {
name: Navigation.Transforms,
id: 5,
href: `#${Pathname.Transforms}`,
- isSelected: pathname === Pathname.Transforms
+ isSelected: pathname === Pathname.Transforms,
},
],
},
@@ -196,9 +196,9 @@ export default class Main extends Component {
/>
(
+ render={(props: RouteComponentProps) => (
-
+
)}
/>
@@ -206,7 +206,12 @@ export default class Main extends Component {
path={ROUTES.CREATE_TRANSFORM}
render={(props: RouteComponentProps) => (
-
+
)}
/>
From 42ace95d83fd412daee475ddb971b5892345200b Mon Sep 17 00:00:00 2001
From: Eric Lobdell
Date: Wed, 28 Apr 2021 17:51:14 -0700
Subject: [PATCH 16/93] Added execution interval to Schedule page
---
.../components/Schedule/Schedule.tsx | 70 +++++-
.../CreateTransformForm.tsx | 200 ++++++++++++++----
.../CreateTransformStep3.tsx | 19 +-
.../pages/CreateTransform/utils/constants.ts | 11 -
public/services/TransformService.ts | 8 +
server/clusters/ism/ismPlugin.ts | 27 +++
server/routes/transforms.ts | 17 ++
server/services/TransformService.ts | 43 ++++
8 files changed, 337 insertions(+), 58 deletions(-)
diff --git a/public/pages/CreateTransform/components/Schedule/Schedule.tsx b/public/pages/CreateTransform/components/Schedule/Schedule.tsx
index 9a8e45021..7cbefbbea 100644
--- a/public/pages/CreateTransform/components/Schedule/Schedule.tsx
+++ b/public/pages/CreateTransform/components/Schedule/Schedule.tsx
@@ -27,6 +27,8 @@ import {
EuiTextArea,
EuiFormHelpText,
EuiText,
+ EuiAccordion,
+ EuiHorizontalRule,
} from "@elastic/eui";
import { DelayTimeunitOptions, ScheduleIntervalTimeunitOptions } from "../../utils/constants";
import { ContentPanel } from "../../../../components/ContentPanel";
@@ -36,8 +38,13 @@ interface ScheduleProps {
transformId: string;
transformIdError: string;
jobEnabledByDefault: boolean;
+ interval: number;
+ intervalTimeunit: string;
+ intervalError: string;
pageSize: number;
onChangeJobEnabledByDefault: () => void;
+ onChangeIntervalTime: (e: ChangeEvent) => void;
+ onChangeIntervalTimeunit: (e: ChangeEvent) => void;
onChangePage: (e: ChangeEvent) => void;
}
@@ -52,6 +59,35 @@ const radios = [
},
];
+const selectInterval = (
+ interval: number,
+ intervalTimeunit: string,
+ intervalError: string,
+ onChangeInterval: (e: ChangeEvent) => void,
+ onChangeTimeunit: (value: ChangeEvent) => void
+) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
const timezones = moment.tz.names().map((tz) => ({ label: tz, text: tz }));
export default class Schedule extends Component {
@@ -63,8 +99,13 @@ export default class Schedule extends Component {
const {
isEdit,
jobEnabledByDefault,
+ interval,
+ intervalTimeunit,
+ intervalError,
pageSize,
onChangeJobEnabledByDefault,
+ onChangeIntervalTime,
+ onChangeIntervalTimeunit,
onChangePage,
} = this.props;
return (
@@ -73,7 +114,7 @@ export default class Schedule extends Component {
{!isEdit && (
{
)}
-
-
+ {!isEdit}
+
+
+
+
+ {selectInterval(interval, intervalTimeunit, intervalError, onChangeIntervalTime, onChangeIntervalTimeunit)}
+
+
+
+
+
+
+
+
);
diff --git a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
index 16c135c2c..44b27a4ae 100644
--- a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
@@ -23,7 +23,7 @@ import IndexService from "../../../../services/IndexService";
import { ManagedCatIndex } from "../../../../../server/models/interfaces";
import CreateTransform from "../CreateTransform";
import CreateTransformStep2 from "../CreateTransformStep2";
-import { DimensionItem, FieldItem, IndexItem, MetricItem, Transform } from "../../../../../models/interfaces";
+import { DimensionItem, FieldItem, IndexItem, Transform } from "../../../../../models/interfaces";
import { getErrorMessage } from "../../../../utils/helpers";
import { EMPTY_TRANSFORM } from "../../utils/constants";
import CreateTransformStep3 from "../CreateTransformStep3";
@@ -59,28 +59,16 @@ interface CreateTransformFormState {
allMappings: FieldItem[][];
fields: FieldItem[];
selectedTerms: FieldItem[];
- selectedDimensionField: DimensionItem[];
- selectedMetrics: MetricItem[];
- metricError: string;
- timestamp: EuiComboBoxOptionOption[];
- timestampError: string;
- intervalType: string;
- intervalValue: number;
- timezone: string;
- timeunit: string;
+ selectedGroupField: DimensionItem[];
+ selectedAggregations: MetricItem[]; // Needs to be Map
+ aggregationsError: string;
selectedFields: FieldItem[];
jobEnabledByDefault: boolean;
- continuousJob: string;
- continuousDefinition: string;
interval: number;
intervalError: string;
intervalTimeunit: string;
- cronExpression: string;
- cronTimezone: string;
pageSize: number;
- delayTime: number | undefined;
- delayTimeunit: string;
transformJSON: any;
}
@@ -106,6 +94,11 @@ export default class CreateTransformForm extends Component {
+ if (!(aggregation.min ||
+ aggregation.max ||
+ aggregation.sum ||
+ aggregation.avg ||
+ aggregation.value_count||
+ aggregation.percentiles
+ )) {
+ const errorMsg = "Must specify at least one aggregation for: " + aggregation.source_field.label;
+ this.setState({ submitError: errorMsg, aggregationsError: errorMsg });
+ invalidAggregation = true;
+ error = true;
+ }
+ });
+ //If nothing invalid found, clear error.
+ if (!invalidAggregation) this.setState({ aggregationsError: "" });
+ }
} else if (currentStep == 3) {
//Check if interval is a valid value and is specified.
- const { intervalError, continuousDefinition } = this.state;
- if (continuousDefinition == "fixed") {
- if (intervalError != "") {
- const intervalErrorMsg = "Interval value is required.";
- this.setState({ submitError: intervalErrorMsg, intervalError: intervalErrorMsg });
- error = true;
- }
- }
+ const { intervalError } = this.state;
}
if (error) return;
@@ -193,7 +205,7 @@ export default class CreateTransformForm extends Component {
- const { intervalType, intervalValue, timeunit } = this.state;
- let newJSON = this.state.transformJSON;
- if (intervalType == "calendar") {
- newJSON.transform.dimensions[0].date_histogram.calendar_interval = `1${timeunit}`;
- delete newJSON.transform.dimensions[0].date_histogram["fixed_interval"];
- } else {
- newJSON.transform.dimensions[0].date_histogram.fixed_interval = `${intervalValue}${timeunit}`;
- delete newJSON.transform.dimensions[0].date_histogram["calendar_interval"];
- }
- this.setState({ transformJSON: newJSON });
+ onGroupSelectionChange = (selectedFields: DimensionItem[]): void => {
+ this.setState({ selectedGroupField: selectedFields });
+ };
+
+ onAggregationSelectionChange = (selectedFields: MetricItem[]): void => {
+ this.setState({ selectedAggregations: selectedFields });
};
onChangeJobEnabledByDefault = (): void => {
@@ -269,12 +276,99 @@ export default class CreateTransformForm extends Component): void => {
+ this.setState({ interval: e.target.valueAsNumber });
+ if (e.target.value == "") {
+ const intervalErrorMsg = "Interval value is required.";
+ this.setState({ submitError: intervalErrorMsg, intervalError: intervalErrorMsg });
+ } else {
+ this.setState({ intervalError: "" });
+ }
+ };
+
onChangePage = (e: ChangeEvent): void => {
let newJSON = this.state.transformJSON;
newJSON.transform.page_size = e.target.valueAsNumber;
this.setState({ pageSize: e.target.valueAsNumber, transformJSON: newJSON });
};
+ updateSchedule = (): void => {
+ const { interval, intervalTimeunit } = this.state;
+ let newJSON = this.state.transformJSON;
+
+ newJSON.transform.schedule.interval = {
+ start_time: moment().unix(),
+ unit: `${intervalTimeunit}`,
+ period: `${interval}`,
+ };
+ delete newJSON.transform.schedule["cron"];
+
+ this.setState({ transformJSON: newJSON });
+ };
+
+ onChangeIntervalTimeunit = (e: ChangeEvent): void => {
+ this.setState({ intervalTimeunit: e.target.value });
+ };
+
+ updateGroup = (): void => {
+ const { transformJSON, selectedGroupField } = this.state;
+ let newJSON = transformJSON;
+
+ // Clear the groups fields
+ newJSON.transform.groups = {};
+
+ // Push rest of groups
+ selectedGroupField.map((group) => {
+ if (group.aggregationMethod == "terms") {
+ newJSON.transform.groups.push({
+ terms: {
+ source_field: group.field.label,
+ target_field: "", // needs target_field source, null target_field test
+ },
+ });
+ } else if (group.aggregationMethod == "histogram") {
+ newJSON.transform.groups.push({
+ histogram: {
+ source_field: group.field.label,
+ interval: group.interval,
+ },
+ });
+ } else {
+ newJSON.transform.groups.push({
+ date_histogram: {
+ source_field: group.field.label,
+ // need to fill out other date histogram data
+ }
+ });
+ }
+ });
+ this.setState({ transformJSON: newJSON });
+ };
+
+ updateAggregation = (): void => {
+ const { transformJSON, selectedAggregations } = this.state;
+ let newJSON = transformJSON;
+
+ //Clear the aggregations array before pushing
+ newJSON.transform.aggregations = [];
+
+ //Push all aggregations
+ selectedAggregations.map((aggregation) => {
+ const aggregations = [];
+ if (aggregation.min) aggregations.push({ min: {} });
+ if (aggregation.max) aggregations.push({ max: {} });
+ if (aggregation.sum) aggregations.push({ sum: {} });
+ if (aggregation.avg) aggregations.push({ avg: {} });
+ if (aggregation.value_count) aggregations.push({ value_count: {} });
+ if (aggregation.percentiles) aggregations.push({ percentiles: {} });
+ newJSON.transform.aggregations.push({
+ source_field: aggregation.source_field.label,
+ aggregations: aggregations,
+ });
+ });
+ this.setState({ transformJSON: newJSON });
+ };
+
onSubmit = async (): Promise => {
const { transformId, transformJSON } = this.state;
this.setState({ submitError: "", isSubmitting: true, hasSubmitted: true });
@@ -282,7 +376,9 @@ export default class CreateTransformForm extends Component
void;
+ onChangeIntervalTime: (e: ChangeEvent) => void;
onChangePage: (e: ChangeEvent) => void;
+ onChangeIntervalTimeunit: (e: ChangeEvent) => void;
}
interface CreateTransformState {
@@ -113,8 +118,13 @@ export default class CreateTransformStep3 extends Component
- Specify schedule
+ Specify Schedule
diff --git a/public/pages/CreateTransform/utils/constants.ts b/public/pages/CreateTransform/utils/constants.ts
index 008f1bf34..1ab5afeb0 100644
--- a/public/pages/CreateTransform/utils/constants.ts
+++ b/public/pages/CreateTransform/utils/constants.ts
@@ -15,19 +15,8 @@
export const EMPTY_TRANSFORM = JSON.stringify({
transform: {
- continuous: false,
description: "",
- dimensions: [
- {
- date_histogram: {
- source_field: "",
- fixed_interval: "1h",
- timezone: "UTC",
- },
- },
- ],
enabled: true,
- metrics: [],
page_size: 1000,
roles: [],
schedule: {
diff --git a/public/services/TransformService.ts b/public/services/TransformService.ts
index b31a49fa3..bea66f665 100644
--- a/public/services/TransformService.ts
+++ b/public/services/TransformService.ts
@@ -64,4 +64,12 @@ export default class TransformService {
}
// TODO: implement preview transform
+
+ //Function to search for fields from a source index using GET /${source_index}/_mapping
+ getMappings = async (index: string): Promise> => {
+ const url = `..${NODE_API._MAPPINGS}`;
+ const body = { index: index };
+ const response = (await this.httpClient.post(url, { body: JSON.stringify(body) })) as ServerResponse;
+ return response;
+ };
}
diff --git a/server/clusters/ism/ismPlugin.ts b/server/clusters/ism/ismPlugin.ts
index 4ca75fb72..7a1c63abb 100644
--- a/server/clusters/ism/ismPlugin.ts
+++ b/server/clusters/ism/ismPlugin.ts
@@ -344,4 +344,31 @@ export default function ismPlugin(Client: any, config: any, components: any) {
},
method: "DELETE"
});
+
+ ism.createTransform = ca({
+ url: {
+ fmt: `${API.TRANSFORM_BASE}/<%=transformId%>?refresh=wait_for`,
+ req: {
+ transformId: {
+ type: "string",
+ required: true,
+ },
+ },
+ },
+ needBody: true,
+ method: "PUT",
+ });
+
+ ism.putTransform = ca({
+ url: {
+ fmt: `${API.TRANSFORM_BASE}/<%=transformId%>`,
+ req: {
+ transformId: {
+ type: "string",
+ required: true,
+ },
+ },
+ },
+ method: "PUT",
+ });
}
diff --git a/server/routes/transforms.ts b/server/routes/transforms.ts
index ce7dd0de3..091274118 100644
--- a/server/routes/transforms.ts
+++ b/server/routes/transforms.ts
@@ -84,4 +84,21 @@ export default function (services: NodeServices, router: IRouter) {
},
transformService.deleteTransform
)
+
+ router.put(
+ {
+ path: `${NODE_API.TRANSFORMS}/{id}`,
+ validate: {
+ params: schema.object({
+ id: schema.string(),
+ }),
+ query: schema.object({
+ seqNo: schema.maybe(schema.number()),
+ primaryTerm: schema.maybe(schema.number()),
+ }),
+ body: schema.any(),
+ },
+ },
+ transformService.putTransform
+ );
}
diff --git a/server/services/TransformService.ts b/server/services/TransformService.ts
index 4a1774947..0728d0db1 100644
--- a/server/services/TransformService.ts
+++ b/server/services/TransformService.ts
@@ -269,4 +269,47 @@ export default class TransformService {
})
}
}
+
+ /**
+ * Calls backend Put Transform API
+ */
+ putTransform = async (
+ context: RequestHandlerContext,
+ request: KibanaRequest,
+ response: KibanaResponseFactory
+ ): Promise | ResponseError>> => {
+ try {
+ const { id } = request.params as { id: string };
+ const { seqNo, primaryTerm } = request.query as { seqNo?: string; primaryTerm?: string };
+ let method = "ism.putTransform";
+ let params: PutTransformParams = {
+ transformId: id,
+ if_seq_no: seqNo,
+ if_primary_term: primaryTerm,
+ body: JSON.stringify(request.body),
+ };
+ if (seqNo === undefined || primaryTerm === undefined) {
+ method = "ism.createTransform";
+ params = { transformId: id, body: JSON.stringify(request.body) };
+ }
+ const { callAsCurrentUser: callWithRequest } = this.esDriver.asScoped(request);
+ const putTransformResponse: PutTransformResponse = await callWithRequest(method, params);
+ return response.custom({
+ statusCode: 200,
+ body: {
+ ok: true,
+ response: putTransformResponse,
+ },
+ });
+ } catch (err) {
+ console.error("Index Management - TransformService - putTransform", err);
+ return response.custom({
+ statusCode: 200,
+ body: {
+ ok: false,
+ error: err.message,
+ },
+ });
+ }
+ };
}
From 1ce781d111d8853ce44d96f41c84a2b8ffdfdbde Mon Sep 17 00:00:00 2001
From: Annie
Date: Thu, 29 Apr 2021 12:34:11 -0700
Subject: [PATCH 17/93] Data grid showing up with empty data
---
.../DefineTransforms/DefineTransforms.tsx | 44 ++++++++++++-------
1 file changed, 27 insertions(+), 17 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index 0373f0701..80c1e6eaf 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -13,8 +13,8 @@
* permissions and limitations under the License.
*/
-import { EuiDataGrid, EuiSpacer, EuiText } from "@elastic/eui";
-import React, { Component } from "react";
+import { EuiDataGrid, EuiDataGridColumn, EuiSpacer, EuiText } from "@elastic/eui";
+import React, { Component, createContext, useMemo, useState, useEffect, useContext } from "react";
import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
interface DefineTransformsProps {
@@ -22,18 +22,23 @@ interface DefineTransformsProps {
sourceIndex: string;
}
-interface DefineTransformsState {}
+interface DefineTransformsState {
+ columns: EuiDataGridColumn[];
+ visibleColumns: string[];
+}
+
+export default function DefineTransforms({ transfromId, sourceIndex }: DefineTransformsProps) {
+ const columns = [
+ {
+ id: "name",
+ displayAsText: "Name",
+ },
+ ];
-export default class DefineTransforms extends Component {
- constructor(props: DefineTransformsProps) {
- super(props);
- const { transfromId } = this.props;
- this.state = {};
- }
+ const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id));
- render() {
- const { transfromId, sourceIndex } = this.props;
- return (
+ return (
+ <>
{`Viewing sample data from index ${sourceIndex}, filtered by order.type:sales_order, order.success:true`}
- {/**/}
+ {
+ return null;
+ }}
+ />
- );
- }
+ >
+ );
}
From 9664bf66d4d5803d316642513e90559faec41a49 Mon Sep 17 00:00:00 2001
From: Annie
Date: Thu, 29 Apr 2021 12:34:32 -0700
Subject: [PATCH 18/93] Update DefineTransforms.tsx
---
.../components/DefineTransforms/DefineTransforms.tsx | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index 80c1e6eaf..9e79c8f04 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -14,7 +14,7 @@
*/
import { EuiDataGrid, EuiDataGridColumn, EuiSpacer, EuiText } from "@elastic/eui";
-import React, { Component, createContext, useMemo, useState, useEffect, useContext } from "react";
+import React, { useState } from "react";
import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
interface DefineTransformsProps {
@@ -22,11 +22,6 @@ interface DefineTransformsProps {
sourceIndex: string;
}
-interface DefineTransformsState {
- columns: EuiDataGridColumn[];
- visibleColumns: string[];
-}
-
export default function DefineTransforms({ transfromId, sourceIndex }: DefineTransformsProps) {
const columns = [
{
From 7f0111dbcc4032fd77c5a8ca3fc54feb2d937719 Mon Sep 17 00:00:00 2001
From: Annie
Date: Thu, 29 Apr 2021 15:19:15 -0700
Subject: [PATCH 19/93] Able to show fields on step 2
---
.../DefineTransforms/DefineTransforms.tsx | 13 ++++++-------
.../CreateTransformStep2/CreateTransformStep2.tsx | 2 +-
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index 9e79c8f04..a0ebef615 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -16,19 +16,17 @@
import { EuiDataGrid, EuiDataGridColumn, EuiSpacer, EuiText } from "@elastic/eui";
import React, { useState } from "react";
import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
+import { FieldItem } from "../../../../../models/interfaces";
interface DefineTransformsProps {
transformId: string;
sourceIndex: string;
+ fields: FieldItem[];
}
-export default function DefineTransforms({ transfromId, sourceIndex }: DefineTransformsProps) {
- const columns = [
- {
- id: "name",
- displayAsText: "Name",
- },
- ];
+export default function DefineTransforms({ transfromId, sourceIndex, fields }: DefineTransformsProps) {
+ let columns: EuiDataGridColumn[] = [];
+ fields.map((field: FieldItem) => columns.push({ id: field.label }));
const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id));
@@ -66,6 +64,7 @@ export default function DefineTransforms({ transfromId, sourceIndex }: DefineTra
{`Viewing sample data from index ${sourceIndex}, filtered by order.type:sales_order, order.success:true`}
+
Define transform
-
+
From e00a1ea2c3dc583e75d1f91a6a70ce309f822326 Mon Sep 17 00:00:00 2001
From: Annie
Date: Thu, 29 Apr 2021 15:19:33 -0700
Subject: [PATCH 20/93] Update CreateTransformStep2.tsx
---
.../containers/CreateTransformStep2/CreateTransformStep2.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
index 1c9074f29..9a1c14d1f 100644
--- a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
@@ -48,8 +48,6 @@ export default class CreateTransformStep2 extends Component
From 7ca3e40822c0d7d571bb8536bd90a85623522d90 Mon Sep 17 00:00:00 2001
From: Annie
Date: Thu, 29 Apr 2021 16:23:30 -0700
Subject: [PATCH 21/93] Add pagination
---
.../DefineTransforms/DefineTransforms.tsx | 148 +++++++++++++-----
1 file changed, 105 insertions(+), 43 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index a0ebef615..2bd160f0d 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -14,9 +14,11 @@
*/
import { EuiDataGrid, EuiDataGridColumn, EuiSpacer, EuiText } from "@elastic/eui";
-import React, { useState } from "react";
+import React, { useContext, useEffect, useState } from "react";
import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
import { FieldItem } from "../../../../../models/interfaces";
+import { useCallback } from "react";
+import { useMemo } from "react";
interface DefineTransformsProps {
transformId: string;
@@ -26,54 +28,114 @@ interface DefineTransformsProps {
export default function DefineTransforms({ transfromId, sourceIndex, fields }: DefineTransformsProps) {
let columns: EuiDataGridColumn[] = [];
- fields.map((field: FieldItem) => columns.push({ id: field.label }));
+ fields.map((field: FieldItem) => columns.push({ id: field.label, displayAsText: field.label + " type: " + field.type }));
+
+ const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
+
+ const onChangeItemsPerPage = useCallback(
+ (pageSize) =>
+ setPagination((pagination) => ({
+ ...pagination,
+ pageSize,
+ pageIndex: 0,
+ })),
+ [setPagination]
+ );
+
+ const onChangePage = useCallback((pageIndex) => setPagination((pagination) => ({ ...pagination, pageIndex })), [setPagination]);
+
+ const [sortingColumns, setSortingColumns] = useState([]);
+ const onSort = useCallback(
+ (sortingColumns) => {
+ setSortingColumns(sortingColumns);
+ },
+ [setSortingColumns]
+ );
const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id));
+ // const renderCellValue = useMemo(() => {
+ // return ({ rowIndex, columnId, setCellProps }) => {
+ // // const data = useContext(DataContext);
+ // useEffect(() => {
+ // if (columnId === 'amount') {
+ // if (data.hasOwnProperty(rowIndex)) {
+ // const numeric = parseFloat(
+ // data[rowIndex][columnId].match(/\d+\.\d+/)[0],
+ // 10
+ // );
+ // setCellProps({
+ // style: {
+ // backgroundColor: `rgba(0, 255, 0, ${numeric * 0.0002})`,
+ // },
+ // });
+ // }
+ // }
+ // }, [rowIndex, columnId, setCellProps, data]);
+ //
+ // function getFormatted() {
+ // return data[rowIndex][columnId].formatted
+ // ? data[rowIndex][columnId].formatted
+ // : data[rowIndex][columnId];
+ // }
+ //
+ // return data.hasOwnProperty(rowIndex)
+ // ? getFormatted(rowIndex, columnId)
+ // : null;
+ // };
+ // }, []);
+
return (
- <>
-
- // onShow(ApplyPolicyModal, {
- // indices: selectedItems.map((item: ManagedCatIndex) => item.index),
- // core: this.context,
- // }),
- },
+ // onClick: () =>
+ // onShow(ApplyPolicyModal, {
+ // indices: selectedItems.map((item: ManagedCatIndex) => item.index),
+ // core: this.context,
+ // }),
},
- ]}
- />
- }
- bodyStyles={{ padding: "10px 10px" }}
- title="Select fields to transform"
- titleSize="m"
- >
-
- Original fields with sample data
-
-
- {/*TODO: Substitute "source index", and "filtered by" fields with actual values*/}
-
- {`Viewing sample data from index ${sourceIndex}, filtered by order.type:sales_order, order.success:true`}
-
-
- {
- return null;
- }}
+ },
+ ]}
/>
-
- >
+ }
+ bodyStyles={{ padding: "10px 10px" }}
+ title="Select fields to transform"
+ titleSize="m"
+ >
+
+ Original fields with sample data
+
+
+ {/*TODO: Substitute "source index", and "filtered by" fields with actual values*/}
+
+ {`Viewing sample data from index ${sourceIndex}, filtered by order.type:sales_order, order.success:true`}
+
+
+ {/*TODO: add rowCount*/}
+ {
+ return null;
+ }}
+ sorting={{ columns: sortingColumns, onSort }}
+ pagination={{
+ ...pagination,
+ pageSizeOptions: [5, 10, 50, 100],
+ onChangeItemsPerPage: onChangeItemsPerPage,
+ onChangePage: onChangePage,
+ }}
+ />
+
);
}
From 621582a2808c41b927b85ceae6cd1811ad3dd61f Mon Sep 17 00:00:00 2001
From: Annie
Date: Thu, 29 Apr 2021 17:22:07 -0700
Subject: [PATCH 22/93] Attempt to search sample data
---
.../CreateTransformSteps.tsx | 1 +
.../DefineTransforms/DefineTransforms.tsx | 23 +++++---
.../CreateTransformStep2.tsx | 2 +-
public/services/TransformService.ts | 9 ++++
server/routes/transforms.ts | 10 ++++
server/services/TransformService.ts | 53 ++++++++++++++++++-
utils/constants.ts | 1 +
7 files changed, 90 insertions(+), 9 deletions(-)
diff --git a/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx b/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
index 9d42e6e9a..3b404ea58 100644
--- a/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
+++ b/public/pages/CreateTransform/components/CreateTransformSteps/CreateTransformSteps.tsx
@@ -42,6 +42,7 @@ const setOfSteps = (step: number) => {
},
];
};
+
const CreateTransformSteps = ({ step }: CreateTransformStepsProps) => (
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index 2bd160f0d..ee8479fd6 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -18,19 +18,34 @@ import React, { useContext, useEffect, useState } from "react";
import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
import { FieldItem } from "../../../../../models/interfaces";
import { useCallback } from "react";
-import { useMemo } from "react";
+import { TransformService } from "../../../../services";
interface DefineTransformsProps {
+ transformService: TransformService;
transformId: string;
sourceIndex: string;
fields: FieldItem[];
}
-export default function DefineTransforms({ transfromId, sourceIndex, fields }: DefineTransformsProps) {
+export default function DefineTransforms({ transformService, transfromId, sourceIndex, fields }: DefineTransformsProps) {
let columns: EuiDataGridColumn[] = [];
+
fields.map((field: FieldItem) => columns.push({ id: field.label, displayAsText: field.label + " type: " + field.type }));
+ const [loading, setLoading] = useState(true);
+
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
+ const [sortingColumns, setSortingColumns] = useState([]);
+ const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id));
+
+ const fetchData = useCallback(async () => {
+ const response = await transformService.searchSampleData(sourceIndex);
+ if (response.ok) console.log("Successfully searched sample data: " + JSON.stringify(response));
+ }, []);
+
+ // React.useEffect(() => {
+ // fetchData();
+ // }, [ fetchData]);
const onChangeItemsPerPage = useCallback(
(pageSize) =>
@@ -41,10 +56,8 @@ export default function DefineTransforms({ transfromId, sourceIndex, fields }: D
})),
[setPagination]
);
-
const onChangePage = useCallback((pageIndex) => setPagination((pagination) => ({ ...pagination, pageIndex })), [setPagination]);
- const [sortingColumns, setSortingColumns] = useState([]);
const onSort = useCallback(
(sortingColumns) => {
setSortingColumns(sortingColumns);
@@ -52,8 +65,6 @@ export default function DefineTransforms({ transfromId, sourceIndex, fields }: D
[setSortingColumns]
);
- const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id));
-
// const renderCellValue = useMemo(() => {
// return ({ rowIndex, columnId, setCellProps }) => {
// // const data = useContext(DataContext);
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
index 9a1c14d1f..4d05d7d3e 100644
--- a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
@@ -60,7 +60,7 @@ export default class CreateTransformStep2 extends Component
Define transform
-
+
diff --git a/public/services/TransformService.ts b/public/services/TransformService.ts
index 8fbd0f6f8..1691e3c01 100644
--- a/public/services/TransformService.ts
+++ b/public/services/TransformService.ts
@@ -73,4 +73,13 @@ export default class TransformService {
const response = (await this.httpClient.post(url, { body: JSON.stringify(body) })) as ServerResponse;
return response;
};
+
+ searchSampleData = async (index: string): Promise> => {
+ const url = `..${NODE_API._SEARCH_SAMPLE_DATA}`;
+ const body = { index: index };
+ const response = (await this.httpClient.get(url, { body: JSON.stringify(body) })) as ServerResponse;
+ //Debug use
+ console.log("response: " + JSON.stringify(response));
+ return response;
+ };
}
diff --git a/server/routes/transforms.ts b/server/routes/transforms.ts
index 851a586dc..340bc8025 100644
--- a/server/routes/transforms.ts
+++ b/server/routes/transforms.ts
@@ -101,4 +101,14 @@ export default function (services: NodeServices, router: IRouter) {
},
transformService.putTransform
);
+
+ router.post(
+ {
+ path: NODE_API._SEARCH_SAMPLE_DATA,
+ validate: {
+ body: schema.any(),
+ },
+ },
+ transformService.searchSampleData
+ );
}
diff --git a/server/services/TransformService.ts b/server/services/TransformService.ts
index cfaca1e71..031d550b0 100644
--- a/server/services/TransformService.ts
+++ b/server/services/TransformService.ts
@@ -13,9 +13,9 @@
* permissions and limitations under the License.
*/
-import { IClusterClient, IKibanaResponse, KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from "kibana/server";
+import { IClusterClient, IKibanaResponse, KibanaRequest, KibanaResponseFactory, RequestHandlerContext, ResponseError } from "kibana/server";
import { ServerResponse } from "../models/types";
-import { GetTransformsResponse } from "../models/interfaces";
+import { GetTransformsResponse, PutTransformParams, PutTransformResponse, SearchResponse } from "../models/interfaces";
import { DocumentTransform, Transform } from "../../models/interfaces";
import _ from "lodash";
@@ -306,4 +306,53 @@ export default class TransformService {
});
}
};
+
+ searchSampleData = async (
+ context: RequestHandlerContext,
+ request: KibanaRequest,
+ response: KibanaResponseFactory
+ ): Promise>> => {
+ try {
+ const { index } = request.body as { index: string };
+ const params = { index: index };
+ const { from, size, search, sortField, sortDirection } = request.query as {
+ from: string;
+ size: string;
+ search: string;
+ sortField: string;
+ sortDirection: string;
+ };
+ const { callAsCurrentUser: callWithRequest } = this.esDriver.asScoped(request);
+ const searchResponse: SearchResponse = await callWithRequest(request, "search", params);
+
+ //Debug use
+ console.log(JSON.stringify(searchResponse));
+
+ return response.custom({
+ statusCode: 200,
+ body: {
+ ok: true,
+ response: {},
+ },
+ });
+ } catch (err) {
+ if (err.statusCode === 404 && err.body.error.type === "index_not_found_exception") {
+ return response.custom({
+ statusCode: 200,
+ body: {
+ ok: true,
+ response: {},
+ },
+ });
+ }
+ console.error("Index Management - TransformService - searchSampleData", err);
+ return response.custom({
+ statusCode: 200,
+ body: {
+ ok: false,
+ error: err.message,
+ },
+ });
+ }
+ };
}
diff --git a/utils/constants.ts b/utils/constants.ts
index 7db04af57..bfdbfc2b7 100644
--- a/utils/constants.ts
+++ b/utils/constants.ts
@@ -16,6 +16,7 @@
export const BASE_API_PATH = "/api/ism";
export const NODE_API = Object.freeze({
_SEARCH: `${BASE_API_PATH}/_search`,
+ _SEARCH_SAMPLE_DATA: `${BASE_API_PATH}/_searchSampleData`,
_INDICES: `${BASE_API_PATH}/_indices`,
_MAPPINGS: `${BASE_API_PATH}/_mappings`,
APPLY_POLICY: `${BASE_API_PATH}/applyPolicy`,
From a7e78bc6a53cb8b1787933bd1d6f859fed27a392 Mon Sep 17 00:00:00 2001
From: Annie
Date: Thu, 29 Apr 2021 23:44:52 -0700
Subject: [PATCH 23/93] Update DefineTransforms.tsx
---
.../components/DefineTransforms/DefineTransforms.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index ee8479fd6..038482d3c 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -143,6 +143,7 @@ export default function DefineTransforms({ transformService, transfromId, source
pagination={{
...pagination,
pageSizeOptions: [5, 10, 50, 100],
+ pageSizeOptions: [5, 10, 20, 50],
onChangeItemsPerPage: onChangeItemsPerPage,
onChangePage: onChangePage,
}}
From 8f6eb8a931b2508323894c0cd66ae10338e6a315 Mon Sep 17 00:00:00 2001
From: Annie
Date: Thu, 29 Apr 2021 23:44:59 -0700
Subject: [PATCH 24/93] Update DefineTransforms.tsx
---
.../components/DefineTransforms/DefineTransforms.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index 038482d3c..abb262361 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -142,7 +142,6 @@ export default function DefineTransforms({ transformService, transfromId, source
sorting={{ columns: sortingColumns, onSort }}
pagination={{
...pagination,
- pageSizeOptions: [5, 10, 50, 100],
pageSizeOptions: [5, 10, 20, 50],
onChangeItemsPerPage: onChangeItemsPerPage,
onChangePage: onChangePage,
From 769e2fefaf76b9ec86a3673cdb7034a8919c7f26 Mon Sep 17 00:00:00 2001
From: Annie
Date: Thu, 29 Apr 2021 23:45:16 -0700
Subject: [PATCH 25/93] Update DefineTransforms.tsx
---
.../components/DefineTransforms/DefineTransforms.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index abb262361..7596a3e80 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -132,7 +132,7 @@ export default function DefineTransforms({ transformService, transfromId, source
{/*TODO: add rowCount*/}
Date: Fri, 30 Apr 2021 13:20:40 -0700
Subject: [PATCH 26/93] Getting search response
---
.../DefineTransforms/DefineTransforms.tsx | 39 ++++++++++++-------
.../CreateTransformStep2.tsx | 8 +++-
public/services/TransformService.ts | 9 +++--
server/routes/transforms.ts | 8 ++--
server/services/TransformService.ts | 38 ++++++++++++------
5 files changed, 69 insertions(+), 33 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index 7596a3e80..4cf4cb027 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -14,20 +14,22 @@
*/
import { EuiDataGrid, EuiDataGridColumn, EuiSpacer, EuiText } from "@elastic/eui";
-import React, { useContext, useEffect, useState } from "react";
+import { CoreStart } from "kibana/public";
+import React, { useState, useCallback } from "react";
import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
import { FieldItem } from "../../../../../models/interfaces";
-import { useCallback } from "react";
import { TransformService } from "../../../../services";
+import { getErrorMessage } from "../../../../utils/helpers";
interface DefineTransformsProps {
transformService: TransformService;
+ notifications: CoreStart["notifications"];
transformId: string;
sourceIndex: string;
fields: FieldItem[];
}
-export default function DefineTransforms({ transformService, transfromId, sourceIndex, fields }: DefineTransformsProps) {
+export default function DefineTransforms({ transformService, notifications, transfromId, sourceIndex, fields }: DefineTransformsProps) {
let columns: EuiDataGridColumn[] = [];
fields.map((field: FieldItem) => columns.push({ id: field.label, displayAsText: field.label + " type: " + field.type }));
@@ -37,15 +39,28 @@ export default function DefineTransforms({ transformService, transfromId, source
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
const [sortingColumns, setSortingColumns] = useState([]);
const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id));
+ const [data, setData] = useState([]);
+ const [dataCount, setDataCount] = useState(0);
const fetchData = useCallback(async () => {
- const response = await transformService.searchSampleData(sourceIndex);
- if (response.ok) console.log("Successfully searched sample data: " + JSON.stringify(response));
- }, []);
+ console.log("Entering fetchData...");
+ try {
+ const response = await transformService.searchSampleData(sourceIndex);
- // React.useEffect(() => {
- // fetchData();
- // }, [ fetchData]);
+ if (response.ok) {
+ //Debug use
+ console.log("Successfully searched sample data: " + JSON.stringify(response));
+ setData(response.response.data);
+ setDataCount(response.response.total);
+ }
+ } catch (err) {
+ notifications.toasts.addDanger(getErrorMessage(err, "There was a problem loading the rollups"));
+ }
+ }, [sourceIndex]);
+
+ React.useEffect(() => {
+ fetchData();
+ }, [fetchData]);
const onChangeItemsPerPage = useCallback(
(pageSize) =>
@@ -135,10 +150,8 @@ export default function DefineTransforms({ transformService, transfromId, source
aria-label="Define transforms"
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
- rowCount={200}
- renderCellValue={() => {
- return null;
- }}
+ rowCount={dataCount}
+ renderCellValue={({ rowIndex, columnId }) => data[rowIndex][columnId]}
sorting={{ columns: sortingColumns, onSort }}
pagination={{
...pagination,
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
index 4d05d7d3e..851fbb4c7 100644
--- a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
@@ -60,7 +60,13 @@ export default class CreateTransformStep2 extends ComponentDefine transform
-
+
diff --git a/public/services/TransformService.ts b/public/services/TransformService.ts
index 1691e3c01..eaee7728f 100644
--- a/public/services/TransformService.ts
+++ b/public/services/TransformService.ts
@@ -15,7 +15,7 @@
import { HttpSetup } from "kibana/public";
import { ServerResponse } from "../../server/models/types";
-import { GetTransformsResponse, PutTransformResponse } from "../../server/models/interfaces";
+import { GetFieldsResponse, GetTransformsResponse, PutTransformResponse, SearchSampleDataResponse } from "../../server/models/interfaces";
import { NODE_API } from "../../utils/constants";
import { DocumentTransform, Transform } from "../../models/interfaces";
@@ -75,9 +75,10 @@ export default class TransformService {
};
searchSampleData = async (index: string): Promise> => {
- const url = `..${NODE_API._SEARCH_SAMPLE_DATA}`;
- const body = { index: index };
- const response = (await this.httpClient.get(url, { body: JSON.stringify(body) })) as ServerResponse;
+ //Debug use
+ console.log("Entering browser side service...");
+ const url = `..${NODE_API._SEARCH_SAMPLE_DATA}/${index}`;
+ const response = (await this.httpClient.get(url)) as ServerResponse;
//Debug use
console.log("response: " + JSON.stringify(response));
return response;
diff --git a/server/routes/transforms.ts b/server/routes/transforms.ts
index 340bc8025..41c50dd60 100644
--- a/server/routes/transforms.ts
+++ b/server/routes/transforms.ts
@@ -102,11 +102,13 @@ export default function (services: NodeServices, router: IRouter) {
transformService.putTransform
);
- router.post(
+ router.get(
{
- path: NODE_API._SEARCH_SAMPLE_DATA,
+ path: `${NODE_API._SEARCH_SAMPLE_DATA}/{index}`,
validate: {
- body: schema.any(),
+ params: schema.object({
+ index: schema.string(),
+ }),
},
},
transformService.searchSampleData
diff --git a/server/services/TransformService.ts b/server/services/TransformService.ts
index 031d550b0..cdb8cde16 100644
--- a/server/services/TransformService.ts
+++ b/server/services/TransformService.ts
@@ -15,7 +15,13 @@
import { IClusterClient, IKibanaResponse, KibanaRequest, KibanaResponseFactory, RequestHandlerContext, ResponseError } from "kibana/server";
import { ServerResponse } from "../models/types";
-import { GetTransformsResponse, PutTransformParams, PutTransformResponse, SearchResponse } from "../models/interfaces";
+import {
+ GetTransformsResponse,
+ PutTransformParams,
+ PutTransformResponse,
+ SearchResponse,
+ SearchSampleDataResponse,
+} from "../models/interfaces";
import { DocumentTransform, Transform } from "../../models/interfaces";
import _ from "lodash";
@@ -313,17 +319,19 @@ export default class TransformService {
response: KibanaResponseFactory
): Promise>> => {
try {
- const { index } = request.body as { index: string };
+ // Debug use
+ console.log("Entering server side service...");
+ // const { from, size, search, sortField, sortDirection } = request.query as {
+ // from: string;
+ // size: string;
+ // search: string;
+ // sortField: string;
+ // sortDirection: string;
+ // };
+ const { index } = request.params as { index: string };
const params = { index: index };
- const { from, size, search, sortField, sortDirection } = request.query as {
- from: string;
- size: string;
- search: string;
- sortField: string;
- sortDirection: string;
- };
const { callAsCurrentUser: callWithRequest } = this.esDriver.asScoped(request);
- const searchResponse: SearchResponse = await callWithRequest(request, "search", params);
+ const searchResponse: SearchResponse = await callWithRequest("search", params);
//Debug use
console.log(JSON.stringify(searchResponse));
@@ -332,7 +340,10 @@ export default class TransformService {
statusCode: 200,
body: {
ok: true,
- response: {},
+ response: {
+ total: searchResponse.hits.total,
+ data: searchResponse.hits.hits,
+ },
},
});
} catch (err) {
@@ -341,7 +352,10 @@ export default class TransformService {
statusCode: 200,
body: {
ok: true,
- response: {},
+ response: {
+ total: 0,
+ data: [],
+ },
},
});
}
From 6725203f4e76db61b2651d2860040f898a489a18 Mon Sep 17 00:00:00 2001
From: Annie
Date: Fri, 30 Apr 2021 13:48:38 -0700
Subject: [PATCH 27/93] Showing correct total number
---
.../components/DefineTransforms/DefineTransforms.tsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index 4cf4cb027..b3979a29b 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -51,7 +51,7 @@ export default function DefineTransforms({ transformService, notifications, tran
//Debug use
console.log("Successfully searched sample data: " + JSON.stringify(response));
setData(response.response.data);
- setDataCount(response.response.total);
+ setDataCount(response.response.total.value);
}
} catch (err) {
notifications.toasts.addDanger(getErrorMessage(err, "There was a problem loading the rollups"));
@@ -151,7 +151,8 @@ export default function DefineTransforms({ transformService, notifications, tran
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
rowCount={dataCount}
- renderCellValue={({ rowIndex, columnId }) => data[rowIndex][columnId]}
+ // renderCellValue={({ rowIndex, columnId }) => data[rowIndex][columnId]}
+ renderCellValue={({}) => null}
sorting={{ columns: sortingColumns, onSort }}
pagination={{
...pagination,
From 81462cd34ccf16b08c0f3e597680ffcd656f47ca Mon Sep 17 00:00:00 2001
From: Annie
Date: Fri, 30 Apr 2021 14:46:16 -0700
Subject: [PATCH 28/93] Able to reach data at certain field, but still need to
finish render method
---
.../DefineTransforms/DefineTransforms.tsx | 57 ++++++++-----------
1 file changed, 24 insertions(+), 33 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index b3979a29b..ef2f0a388 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -15,11 +15,13 @@
import { EuiDataGrid, EuiDataGridColumn, EuiSpacer, EuiText } from "@elastic/eui";
import { CoreStart } from "kibana/public";
-import React, { useState, useCallback } from "react";
+import React, { useState, useCallback, useEffect } from "react";
import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
import { FieldItem } from "../../../../../models/interfaces";
import { TransformService } from "../../../../services";
import { getErrorMessage } from "../../../../utils/helpers";
+import { useMemo } from "react";
+import * as repl from "repl";
interface DefineTransformsProps {
transformService: TransformService;
@@ -52,6 +54,7 @@ export default function DefineTransforms({ transformService, notifications, tran
console.log("Successfully searched sample data: " + JSON.stringify(response));
setData(response.response.data);
setDataCount(response.response.total.value);
+ console.log("First item: " + JSON.stringify(response.response.data[0]));
}
} catch (err) {
notifications.toasts.addDanger(getErrorMessage(err, "There was a problem loading the rollups"));
@@ -80,36 +83,19 @@ export default function DefineTransforms({ transformService, notifications, tran
[setSortingColumns]
);
- // const renderCellValue = useMemo(() => {
- // return ({ rowIndex, columnId, setCellProps }) => {
- // // const data = useContext(DataContext);
- // useEffect(() => {
- // if (columnId === 'amount') {
- // if (data.hasOwnProperty(rowIndex)) {
- // const numeric = parseFloat(
- // data[rowIndex][columnId].match(/\d+\.\d+/)[0],
- // 10
- // );
- // setCellProps({
- // style: {
- // backgroundColor: `rgba(0, 255, 0, ${numeric * 0.0002})`,
- // },
- // });
- // }
- // }
- // }, [rowIndex, columnId, setCellProps, data]);
- //
- // function getFormatted() {
- // return data[rowIndex][columnId].formatted
- // ? data[rowIndex][columnId].formatted
- // : data[rowIndex][columnId];
- // }
- //
- // return data.hasOwnProperty(rowIndex)
- // ? getFormatted(rowIndex, columnId)
- // : null;
- // };
- // }, []);
+ const renderCellValue = useMemo(() => {
+ return ({ rowIndex, columnId }) => {
+ // const data = useContext(DataContext);
+ useEffect(() => {
+ {
+ //Debug use
+ console.log("rowIndex: " + rowIndex + " columnId: " + columnId + " data: " + JSON.stringify(data[rowIndex]._source[columnId]));
+ return data[rowIndex]._source[columnId] ? data[rowIndex]._source[columnId] : null;
+ // return null;
+ }
+ }, [rowIndex, columnId, setCellProps, data]);
+ };
+ }, []);
return (
data[rowIndex][columnId]}
- renderCellValue={({}) => null}
+ renderCellValue={({ rowIndex, columnId }) => {
+ //Debug use
+ console.log("rowIndex: " + rowIndex + " columnId: " + columnId + " data: " + JSON.stringify(data[rowIndex]._source[columnId]));
+ // return data[rowIndex]._source[columnId] ? data[rowIndex]._source[columnId] : null
+ return null;
+ }}
+ // renderCellValue={({}) => null}
sorting={{ columns: sortingColumns, onSort }}
pagination={{
...pagination,
From 475786575119a1d1bf978670ba16eabf2a3cd397 Mon Sep 17 00:00:00 2001
From: Annie
Date: Fri, 30 Apr 2021 20:53:00 -0700
Subject: [PATCH 29/93] Able to show first 10 data
---
.../DefineTransforms/DefineTransforms.tsx | 37 ++++++++++++-------
1 file changed, 23 insertions(+), 14 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index ef2f0a388..42b4eb9ae 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -13,15 +13,14 @@
* permissions and limitations under the License.
*/
-import { EuiDataGrid, EuiDataGridColumn, EuiSpacer, EuiText } from "@elastic/eui";
+import { EuiDataGrid, EuiDataGridCellValueElementProps, EuiDataGridColumn, EuiSpacer, EuiText } from "@elastic/eui";
import { CoreStart } from "kibana/public";
-import React, { useState, useCallback, useEffect } from "react";
+import React, { useState, useCallback, useEffect, Component } from "react";
import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
import { FieldItem } from "../../../../../models/interfaces";
import { TransformService } from "../../../../services";
import { getErrorMessage } from "../../../../utils/helpers";
import { useMemo } from "react";
-import * as repl from "repl";
interface DefineTransformsProps {
transformService: TransformService;
@@ -46,6 +45,7 @@ export default function DefineTransforms({ transformService, notifications, tran
const fetchData = useCallback(async () => {
console.log("Entering fetchData...");
+ setLoading(true);
try {
const response = await transformService.searchSampleData(sourceIndex);
@@ -54,11 +54,12 @@ export default function DefineTransforms({ transformService, notifications, tran
console.log("Successfully searched sample data: " + JSON.stringify(response));
setData(response.response.data);
setDataCount(response.response.total.value);
- console.log("First item: " + JSON.stringify(response.response.data[0]));
+ // console.log("First item: " + JSON.stringify(response.response.data[0]));
}
} catch (err) {
notifications.toasts.addDanger(getErrorMessage(err, "There was a problem loading the rollups"));
}
+ setLoading(false);
}, [sourceIndex]);
React.useEffect(() => {
@@ -84,16 +85,16 @@ export default function DefineTransforms({ transformService, notifications, tran
);
const renderCellValue = useMemo(() => {
- return ({ rowIndex, columnId }) => {
+ return ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => {
// const data = useContext(DataContext);
useEffect(() => {
{
//Debug use
console.log("rowIndex: " + rowIndex + " columnId: " + columnId + " data: " + JSON.stringify(data[rowIndex]._source[columnId]));
- return data[rowIndex]._source[columnId] ? data[rowIndex]._source[columnId] : null;
- // return null;
+ if (!loading && data.hasOwnProperty(rowIndex)) return data[rowIndex]._source[columnId] ? data[rowIndex]._source[columnId] : null;
+ return null;
}
- }, [rowIndex, columnId, setCellProps, data]);
+ }, [rowIndex, columnId, data]);
};
}, []);
@@ -137,12 +138,20 @@ export default function DefineTransforms({ transformService, notifications, tran
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
rowCount={dataCount}
- renderCellValue={({ rowIndex, columnId }) => {
- //Debug use
- console.log("rowIndex: " + rowIndex + " columnId: " + columnId + " data: " + JSON.stringify(data[rowIndex]._source[columnId]));
- // return data[rowIndex]._source[columnId] ? data[rowIndex]._source[columnId] : null
- return null;
- }}
+ renderCellValue={
+ ({ rowIndex, columnId }) => {
+ //Debug use
+ console.log("rowIndex: " + rowIndex + " columnId: " + columnId + " data: " + JSON.stringify(data[rowIndex]._source[columnId]));
+ // return data[rowIndex];
+ if (!loading && data.hasOwnProperty(rowIndex))
+ return data[rowIndex]._source[columnId] ? data[rowIndex]._source[columnId] : null;
+ return null;
+ }
+
+ // ({ rowIndex, columnId }) =>
+ // `${rowIndex}, ${columnId}`
+ // renderCellValue
+ }
// renderCellValue={({}) => null}
sorting={{ columns: sortingColumns, onSort }}
pagination={{
From 6fd8f5d3f3dcd718da8e69b497bea7abb70a69b0 Mon Sep 17 00:00:00 2001
From: Annie
Date: Fri, 30 Apr 2021 21:13:09 -0700
Subject: [PATCH 30/93] Update renderCellValue
---
.../DefineTransforms/DefineTransforms.tsx | 59 +++++++++++--------
1 file changed, 33 insertions(+), 26 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index 42b4eb9ae..ac723ab40 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -21,6 +21,7 @@ import { FieldItem } from "../../../../../models/interfaces";
import { TransformService } from "../../../../services";
import { getErrorMessage } from "../../../../utils/helpers";
import { useMemo } from "react";
+import { RollupQueryParams } from "../../../Rollups/models/interfaces";
interface DefineTransformsProps {
transformService: TransformService;
@@ -38,6 +39,8 @@ export default function DefineTransforms({ transformService, notifications, tran
const [loading, setLoading] = useState(true);
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
+ const [from, setFrom] = useState(0);
+ const [size, setSize] = useState(10);
const [sortingColumns, setSortingColumns] = useState([]);
const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id));
const [data, setData] = useState([]);
@@ -47,7 +50,7 @@ export default function DefineTransforms({ transformService, notifications, tran
console.log("Entering fetchData...");
setLoading(true);
try {
- const response = await transformService.searchSampleData(sourceIndex);
+ const response = await transformService.searchSampleData(sourceIndex, { from, size });
if (response.ok) {
//Debug use
@@ -67,15 +70,26 @@ export default function DefineTransforms({ transformService, notifications, tran
}, [fetchData]);
const onChangeItemsPerPage = useCallback(
- (pageSize) =>
+ (pageSize) => {
setPagination((pagination) => ({
...pagination,
pageSize,
pageIndex: 0,
- })),
+ }));
+ setFrom(0);
+ setSize(pageSize);
+ },
+ [setPagination]
+ );
+ const onChangePage = useCallback(
+ (pageIndex) => {
+ setPagination((pagination) => ({ ...pagination, pageIndex }));
+ setFrom(pageIndex * size);
+ //debug use
+ console.log("From: " + pageIndex * size);
+ },
[setPagination]
);
- const onChangePage = useCallback((pageIndex) => setPagination((pagination) => ({ ...pagination, pageIndex })), [setPagination]);
const onSort = useCallback(
(sortingColumns) => {
@@ -84,19 +98,12 @@ export default function DefineTransforms({ transformService, notifications, tran
[setSortingColumns]
);
- const renderCellValue = useMemo(() => {
- return ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => {
- // const data = useContext(DataContext);
- useEffect(() => {
- {
- //Debug use
- console.log("rowIndex: " + rowIndex + " columnId: " + columnId + " data: " + JSON.stringify(data[rowIndex]._source[columnId]));
- if (!loading && data.hasOwnProperty(rowIndex)) return data[rowIndex]._source[columnId] ? data[rowIndex]._source[columnId] : null;
- return null;
- }
- }, [rowIndex, columnId, data]);
- };
- }, []);
+ const renderCellValue = ({ rowIndex, columnId }) => {
+ //Debug use
+ console.log("rowIndex: " + rowIndex + " columnId: " + columnId + " data: " + JSON.stringify(data[rowIndex]._source[columnId]));
+ if (!loading && data.hasOwnProperty(rowIndex)) return data[rowIndex]._source[columnId] ? data[rowIndex]._source[columnId] : null;
+ return null;
+ };
return (
{
- //Debug use
- console.log("rowIndex: " + rowIndex + " columnId: " + columnId + " data: " + JSON.stringify(data[rowIndex]._source[columnId]));
- // return data[rowIndex];
- if (!loading && data.hasOwnProperty(rowIndex))
- return data[rowIndex]._source[columnId] ? data[rowIndex]._source[columnId] : null;
- return null;
- }
+ // ({ rowIndex, columnId }) => {
+ // //Debug use
+ // console.log("rowIndex: " + rowIndex + " columnId: " + columnId + " data: " + JSON.stringify(data[rowIndex]._source[columnId]));
+ // // return data[rowIndex];
+ // if (!loading && data.hasOwnProperty(rowIndex))
+ // return data[rowIndex]._source[columnId] ? data[rowIndex]._source[columnId] : null;
+ // return null;
+ // }
// ({ rowIndex, columnId }) =>
// `${rowIndex}, ${columnId}`
- // renderCellValue
+ renderCellValue
}
// renderCellValue={({}) => null}
sorting={{ columns: sortingColumns, onSort }}
From 16fefd3f5c863e764809c53626ea1ace7d6121aa Mon Sep 17 00:00:00 2001
From: Annie
Date: Sun, 2 May 2021 15:47:26 -0700
Subject: [PATCH 31/93] Add popover options
---
.../DefineTransforms/DefineTransforms.tsx | 134 ++++++++++++++----
.../CreateTransformStep2.tsx | 14 +-
2 files changed, 122 insertions(+), 26 deletions(-)
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index ac723ab40..a61964710 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -13,15 +13,14 @@
* permissions and limitations under the License.
*/
-import { EuiDataGrid, EuiDataGridCellValueElementProps, EuiDataGridColumn, EuiSpacer, EuiText } from "@elastic/eui";
+import { EuiDataGrid, EuiDataGridColumn, EuiSpacer, EuiText } from "@elastic/eui";
import { CoreStart } from "kibana/public";
-import React, { useState, useCallback, useEffect, Component } from "react";
+import React, { useState, useCallback } from "react";
import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
import { FieldItem } from "../../../../../models/interfaces";
import { TransformService } from "../../../../services";
import { getErrorMessage } from "../../../../utils/helpers";
-import { useMemo } from "react";
-import { RollupQueryParams } from "../../../Rollups/models/interfaces";
+import { isNumericMapping } from "../../utils/helpers";
interface DefineTransformsProps {
transformService: TransformService;
@@ -29,12 +28,98 @@ interface DefineTransformsProps {
transformId: string;
sourceIndex: string;
fields: FieldItem[];
+ onGroupSelectionChange: void;
+ onAggregationSelectionChange: void;
}
-export default function DefineTransforms({ transformService, notifications, transfromId, sourceIndex, fields }: DefineTransformsProps) {
+export default function DefineTransforms({
+ transformService,
+ notifications,
+ transfromId,
+ sourceIndex,
+ fields,
+ onGroupSelectionChange,
+ onAggregationSelectionChange,
+}: DefineTransformsProps) {
let columns: EuiDataGridColumn[] = [];
- fields.map((field: FieldItem) => columns.push({ id: field.label, displayAsText: field.label + " type: " + field.type }));
+ fields.map((field: FieldItem) => {
+ const isNumeric = isNumericMapping(field.type);
+ const isDate = field.type == "date";
+ columns.push({
+ id: field.label,
+ displayAsText: field.label + " type: " + field.type,
+ actions: {
+ showHide: false,
+ showMoveLeft: false,
+ showMoveRight: false,
+ showSortAsc: false,
+ showSortDesc: false,
+ additional: [
+ {
+ label: "Group by histogram ",
+ onClick: () => {},
+ size: "xs",
+ color: isNumeric ? "text" : "subdued",
+ },
+ {
+ label: "Group by date histogram ",
+ onClick: () => {},
+ size: "xs",
+ color: isDate ? "text" : "subdued",
+ },
+ {
+ label: "Group by terms ",
+ onClick: () => {},
+ size: "xs",
+ color: "text",
+ },
+ {
+ label: "Aggregate by sum ",
+ onClick: () => {},
+ size: "xs",
+ color: "text",
+ },
+ {
+ label: "Aggregate by max ",
+ onClick: () => {},
+ size: "xs",
+ color: "text",
+ },
+ {
+ label: "Aggregate by min ",
+ onClick: () => {},
+ size: "xs",
+ color: "text",
+ },
+ {
+ label: "Aggregate by avg ",
+ onClick: () => {},
+ size: "xs",
+ color: "text",
+ },
+ {
+ label: "Aggregate by count ",
+ onClick: () => {},
+ size: "xs",
+ color: "text",
+ },
+ {
+ label: "Aggregate by percentile ",
+ onClick: () => {},
+ size: "xs",
+ color: "text",
+ },
+ {
+ label: "Aggregate by scripted metrics ",
+ onClick: () => {},
+ size: "xs",
+ color: "text",
+ },
+ ],
+ },
+ });
+ });
const [loading, setLoading] = useState(true);
@@ -107,25 +192,24 @@ export default function DefineTransforms({ transformService, notifications, tran
return (
- // onShow(ApplyPolicyModal, {
- // indices: selectedItems.map((item: ManagedCatIndex) => item.index),
- // core: this.context,
- // }),
- },
- },
- ]}
- />
- }
+ //TODO: Add action to enter full screen view
+ // actions={
+ //
+ // // onShow(ApplyPolicyModal, {
+ // // indices: selectedItems.map((item: ManagedCatIndex) => item.index),
+ // // core: this.context,
+ // // }),
+ // },
+ // },
+ // ]}
+ // />
+ // }
bodyStyles={{ padding: "10px 10px" }}
title="Select fields to transform"
titleSize="m"
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
index 851fbb4c7..0c2496c6e 100644
--- a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
@@ -29,6 +29,8 @@ interface CreateTransformStep2Props extends RouteComponentProps {
currentStep: number;
sourceIndex: string;
fields: FieldItem[];
+ onGroupSelectionChange: void;
+ onAggregationSelectionChange: void;
}
export default class CreateTransformStep2 extends Component {
@@ -46,7 +48,15 @@ export default class CreateTransformStep2 extends Component
From e374f1911b214ce995c433f9965de257959992da Mon Sep 17 00:00:00 2001
From: Annie
Date: Sun, 2 May 2021 16:51:48 -0700
Subject: [PATCH 32/93] Able to create transform with group by histogram
---
models/interfaces.ts | 12 +++++
.../DefineTransforms/DefineTransforms.tsx | 48 ++++++++++++++-----
.../CreateTransformForm.tsx | 48 ++++++++++---------
.../CreateTransformStep2.tsx | 4 +-
public/services/TransformService.ts | 4 +-
server/models/interfaces.ts | 11 +++++
server/services/TransformService.ts | 17 +++----
7 files changed, 98 insertions(+), 46 deletions(-)
diff --git a/models/interfaces.ts b/models/interfaces.ts
index 8f57048d0..4f3f3fa55 100644
--- a/models/interfaces.ts
+++ b/models/interfaces.ts
@@ -250,3 +250,15 @@ export interface RollupMetricItem {
}
];
}
+export interface GroupItem {
+ sourceField: FieldItem;
+ targetField: string;
+ interval?: number;
+ aggregationMethod: GROUP_TYPES;
+}
+
+export enum GROUP_TYPES {
+ histogram = "histogram",
+ dateHistogram = "date_histogram",
+ terms = "terms",
+}
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index a61964710..503928712 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -15,9 +15,9 @@
import { EuiDataGrid, EuiDataGridColumn, EuiSpacer, EuiText } from "@elastic/eui";
import { CoreStart } from "kibana/public";
-import React, { useState, useCallback } from "react";
-import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
-import { FieldItem } from "../../../../../models/interfaces";
+import React, { useCallback, useState } from "react";
+import { ContentPanel } from "../../../../components/ContentPanel";
+import { FieldItem, GROUP_TYPES, GroupItem } from "../../../../../models/interfaces";
import { TransformService } from "../../../../services";
import { getErrorMessage } from "../../../../utils/helpers";
import { isNumericMapping } from "../../utils/helpers";
@@ -28,7 +28,7 @@ interface DefineTransformsProps {
transformId: string;
sourceIndex: string;
fields: FieldItem[];
- onGroupSelectionChange: void;
+ onGroupSelectionChange: (selectedFields: GroupItem[]) => void;
onAggregationSelectionChange: void;
}
@@ -46,6 +46,7 @@ export default function DefineTransforms({
fields.map((field: FieldItem) => {
const isNumeric = isNumericMapping(field.type);
const isDate = field.type == "date";
+ // TODO: Handle the available options according to column types
columns.push({
id: field.label,
displayAsText: field.label + " type: " + field.type,
@@ -58,19 +59,40 @@ export default function DefineTransforms({
additional: [
{
label: "Group by histogram ",
- onClick: () => {},
+ onClick: () => {
+ groupSelection.push({
+ sourceField: field,
+ targetField: `${field.label}_${GROUP_TYPES.histogram}`,
+ aggregationMethod: GROUP_TYPES.histogram,
+ });
+ onGroupSelectionChange(groupSelection);
+ },
size: "xs",
color: isNumeric ? "text" : "subdued",
},
{
label: "Group by date histogram ",
- onClick: () => {},
+ onClick: () => {
+ groupSelection.push({
+ sourceField: field,
+ targetField: `${field.label}_${GROUP_TYPES.dateHistogram}`,
+ aggregationMethod: GROUP_TYPES.dateHistogram,
+ });
+ onGroupSelectionChange(groupSelection);
+ },
size: "xs",
color: isDate ? "text" : "subdued",
},
{
label: "Group by terms ",
- onClick: () => {},
+ onClick: () => {
+ groupSelection.push({
+ sourceField: field,
+ targetField: `${field.label}_${GROUP_TYPES.terms}`,
+ aggregationMethod: GROUP_TYPES.terms,
+ });
+ onGroupSelectionChange(groupSelection);
+ },
size: "xs",
color: "text",
},
@@ -121,15 +143,16 @@ export default function DefineTransforms({
});
});
- const [loading, setLoading] = useState(true);
+ const [loading, setLoading] = useState(true);
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
- const [from, setFrom] = useState(0);
- const [size, setSize] = useState(10);
+ const [from, setFrom] = useState(0);
+ const [size, setSize] = useState(10);
const [sortingColumns, setSortingColumns] = useState([]);
const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id));
const [data, setData] = useState([]);
- const [dataCount, setDataCount] = useState(0);
+ const [dataCount, setDataCount] = useState(0);
+ const [groupSelection, setGroupSelection] = useState([]);
const fetchData = useCallback(async () => {
console.log("Entering fetchData...");
@@ -252,6 +275,9 @@ export default function DefineTransforms({
onChangePage: onChangePage,
}}
/>
+
+ {/*Debug use*/}
+ {JSON.stringify(groupSelection)}
);
}
diff --git a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
index 0936cbf92..bcd18c5cf 100644
--- a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
@@ -16,13 +16,14 @@
import React, { ChangeEvent, Component } from "react";
import { EuiButton, EuiButtonEmpty, EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem } from "@elastic/eui";
import { RouteComponentProps } from "react-router-dom";
+import moment from "moment";
import { RollupService, TransformService } from "../../../../services";
import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
import IndexService from "../../../../services/IndexService";
import { ManagedCatIndex } from "../../../../../server/models/interfaces";
import CreateTransform from "../CreateTransform";
import CreateTransformStep2 from "../CreateTransformStep2";
-import { DimensionItem, FieldItem, IndexItem, MetricItem, Transform } from "../../../../../models/interfaces";
+import { DimensionItem, FieldItem, GroupItem, IndexItem, MetricItem, Transform } from "../../../../../models/interfaces";
import { getErrorMessage } from "../../../../utils/helpers";
import { EMPTY_TRANSFORM } from "../../utils/constants";
import CreateTransformStep3 from "../CreateTransformStep3";
@@ -60,7 +61,7 @@ interface CreateTransformFormState {
fields: FieldItem[];
selectedTerms: FieldItem[];
- selectedGroupField: DimensionItem[];
+ selectedGroupField: GroupItem[];
selectedAggregations: MetricItem[]; // Needs to be Map
aggregationsError: string;
selectedFields: FieldItem[];
@@ -265,7 +266,7 @@ export default class CreateTransformForm extends Component {
+ onGroupSelectionChange = (selectedFields: GroupItem[]): void => {
this.setState({ selectedGroupField: selectedFields });
};
@@ -319,28 +320,29 @@ export default class CreateTransformForm extends Component {
if (group.aggregationMethod == "terms") {
newJSON.transform.groups.push({
terms: {
- source_field: group.field.label,
- target_field: "", // needs target_field source, null target_field test
+ source_field: group.sourceField.label,
+ target_field: group.targetField, // needs target_field source, null target_field test
},
});
} else if (group.aggregationMethod == "histogram") {
newJSON.transform.groups.push({
histogram: {
- source_field: group.field.label,
- interval: group.interval,
+ source_field: group.sourceField.label,
+ //TODO: Remove the if else condition after implementing define interval
+ interval: group.interval ? group.interval : 5,
},
});
} else {
newJSON.transform.groups.push({
date_histogram: {
- source_field: group.field.label,
+ source_field: group.sourceField.label,
// need to fill out other date histogram data
},
});
@@ -354,22 +356,22 @@ export default class CreateTransformForm extends Component {
- const aggregations = [];
- if (aggregation.min) aggregations.push({ min: {} });
- if (aggregation.max) aggregations.push({ max: {} });
- if (aggregation.sum) aggregations.push({ sum: {} });
- if (aggregation.avg) aggregations.push({ avg: {} });
- if (aggregation.value_count) aggregations.push({ value_count: {} });
- if (aggregation.percentiles) aggregations.push({ percentiles: {} });
- newJSON.transform.aggregations.push({
- source_field: aggregation.source_field.label,
- aggregations: aggregations,
- });
- });
+ // selectedAggregations.map((aggregation) => {
+ // const aggregations = [];
+ // if (aggregation.min) aggregations.push({ min: {} });
+ // if (aggregation.max) aggregations.push({ max: {} });
+ // if (aggregation.sum) aggregations.push({ sum: {} });
+ // if (aggregation.avg) aggregations.push({ avg: {} });
+ // if (aggregation.value_count) aggregations.push({ value_count: {} });
+ // if (aggregation.percentiles) aggregations.push({ percentiles: {} });
+ // newJSON.transform.aggregations.push({
+ // source_field: aggregation.source_field.label,
+ // aggregations: aggregations,
+ // });
+ // });
this.setState({ transformJSON: newJSON });
};
diff --git a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
index 0c2496c6e..bcee8245e 100644
--- a/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformStep2/CreateTransformStep2.tsx
@@ -21,7 +21,7 @@ import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
import CreateTransformSteps from "../../components/CreateTransformSteps";
import { CoreServicesContext } from "../../../../components/core_services";
import DefineTransforms from "../../components/DefineTransforms";
-import { FieldItem } from "../../../../../models/interfaces";
+import { FieldItem, GroupItem } from "../../../../../models/interfaces";
interface CreateTransformStep2Props extends RouteComponentProps {
transformService: TransformService;
@@ -29,7 +29,7 @@ interface CreateTransformStep2Props extends RouteComponentProps {
currentStep: number;
sourceIndex: string;
fields: FieldItem[];
- onGroupSelectionChange: void;
+ onGroupSelectionChange: (selectedFields: GroupItem[]) => void;
onAggregationSelectionChange: void;
}
diff --git a/public/services/TransformService.ts b/public/services/TransformService.ts
index eaee7728f..9282766ce 100644
--- a/public/services/TransformService.ts
+++ b/public/services/TransformService.ts
@@ -74,11 +74,11 @@ export default class TransformService {
return response;
};
- searchSampleData = async (index: string): Promise> => {
+ searchSampleData = async (index: string, queryObject: object): Promise> => {
//Debug use
console.log("Entering browser side service...");
const url = `..${NODE_API._SEARCH_SAMPLE_DATA}/${index}`;
- const response = (await this.httpClient.get(url)) as ServerResponse;
+ const response = (await this.httpClient.get(url, { query: queryObject })) as ServerResponse;
//Debug use
console.log("response: " + JSON.stringify(response));
return response;
diff --git a/server/models/interfaces.ts b/server/models/interfaces.ts
index c36436592..15182efd4 100644
--- a/server/models/interfaces.ts
+++ b/server/models/interfaces.ts
@@ -220,6 +220,17 @@ export interface ExplainAPIManagedIndexMetaData {
enabled: boolean;
}
+export interface SearchSampleDataResponse {
+ total: number;
+ data: {
+ _index: string;
+ _type: string;
+ _id: string;
+ _score: number;
+ _source: object;
+ }[];
+}
+
export interface IndexManagementApi {
[API_ROUTE: string]: string;
diff --git a/server/services/TransformService.ts b/server/services/TransformService.ts
index cdb8cde16..847948163 100644
--- a/server/services/TransformService.ts
+++ b/server/services/TransformService.ts
@@ -321,15 +321,16 @@ export default class TransformService {
try {
// Debug use
console.log("Entering server side service...");
- // const { from, size, search, sortField, sortDirection } = request.query as {
- // from: string;
- // size: string;
- // search: string;
- // sortField: string;
- // sortDirection: string;
- // };
+ const { from, size } = request.query as {
+ from: string;
+ size: string;
+ };
const { index } = request.params as { index: string };
- const params = { index: index };
+ const params = {
+ index: index,
+ from: from,
+ size: size,
+ };
const { callAsCurrentUser: callWithRequest } = this.esDriver.asScoped(request);
const searchResponse: SearchResponse = await callWithRequest("search", params);
From 1aa12cde9263f4642e51cc1bdb5acc55c39dd687 Mon Sep 17 00:00:00 2001
From: Annie
Date: Sun, 2 May 2021 17:20:37 -0700
Subject: [PATCH 33/93] Able to create transform job with group by date
histogram
---
models/interfaces.ts | 8 +--
.../DefineTransforms/DefineTransforms.tsx | 23 ++++----
.../CreateTransformForm.tsx | 52 +++++++++----------
3 files changed, 42 insertions(+), 41 deletions(-)
diff --git a/models/interfaces.ts b/models/interfaces.ts
index 4f3f3fa55..e4c810889 100644
--- a/models/interfaces.ts
+++ b/models/interfaces.ts
@@ -250,12 +250,8 @@ export interface RollupMetricItem {
}
];
}
-export interface GroupItem {
- sourceField: FieldItem;
- targetField: string;
- interval?: number;
- aggregationMethod: GROUP_TYPES;
-}
+
+export type TransformGroupItem = DateHistogramItem | TermsItem | HistogramItem;
export enum GROUP_TYPES {
histogram = "histogram",
diff --git a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
index 503928712..dc2d54912 100644
--- a/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
+++ b/public/pages/CreateTransform/components/DefineTransforms/DefineTransforms.tsx
@@ -61,9 +61,11 @@ export default function DefineTransforms({
label: "Group by histogram ",
onClick: () => {
groupSelection.push({
- sourceField: field,
- targetField: `${field.label}_${GROUP_TYPES.histogram}`,
- aggregationMethod: GROUP_TYPES.histogram,
+ histogram: {
+ source_field: field.label,
+ target_field: `${field.label}_${GROUP_TYPES.histogram}`,
+ interval: 5,
+ },
});
onGroupSelectionChange(groupSelection);
},
@@ -74,9 +76,10 @@ export default function DefineTransforms({
label: "Group by date histogram ",
onClick: () => {
groupSelection.push({
- sourceField: field,
- targetField: `${field.label}_${GROUP_TYPES.dateHistogram}`,
- aggregationMethod: GROUP_TYPES.dateHistogram,
+ terms: {
+ source_field: field.label,
+ target_field: `${field.label}_${GROUP_TYPES.dateHistogram}`,
+ },
});
onGroupSelectionChange(groupSelection);
},
@@ -87,9 +90,11 @@ export default function DefineTransforms({
label: "Group by terms ",
onClick: () => {
groupSelection.push({
- sourceField: field,
- targetField: `${field.label}_${GROUP_TYPES.terms}`,
- aggregationMethod: GROUP_TYPES.terms,
+ date_histogram: {
+ source_field: field.label,
+ target_field: `${field.label}_${GROUP_TYPES.terms}`,
+ calendar_interval: "1d",
+ },
});
onGroupSelectionChange(groupSelection);
},
diff --git a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
index bcd18c5cf..3a75541fd 100644
--- a/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
+++ b/public/pages/CreateTransform/containers/CreateTransformForm/CreateTransformForm.tsx
@@ -320,34 +320,34 @@ export default class CreateTransformForm extends Component {
- if (group.aggregationMethod == "terms") {
- newJSON.transform.groups.push({
- terms: {
- source_field: group.sourceField.label,
- target_field: group.targetField, // needs target_field source, null target_field test
- },
- });
- } else if (group.aggregationMethod == "histogram") {
- newJSON.transform.groups.push({
- histogram: {
- source_field: group.sourceField.label,
- //TODO: Remove the if else condition after implementing define interval
- interval: group.interval ? group.interval : 5,
- },
- });
- } else {
- newJSON.transform.groups.push({
- date_histogram: {
- source_field: group.sourceField.label,
- // need to fill out other date histogram data
- },
- });
- }
- });
+ // selectedGroupField.map((group) => {
+ // if (group.aggregationMethod == "terms") {
+ // newJSON.transform.groups.push({
+ // terms: {
+ // source_field: group.sourceField.label,
+ // target_field: group.targetField, // needs target_field source, null target_field test
+ // },
+ // });
+ // } else if (group.aggregationMethod == "histogram") {
+ // newJSON.transform.groups.push({
+ // histogram: {
+ // source_field: group.sourceField.label,
+ // //TODO: Remove the if else condition after implementing define interval
+ // interval: group.interval ? group.interval : 5,
+ // },
+ // });
+ // } else {
+ // newJSON.transform.groups.push({
+ // date_histogram: {
+ // source_field: group.sourceField.label,
+ // // need to fill out other date histogram data
+ // },
+ // });
+ // }
+ // });
this.setState({ transformJSON: newJSON });
};
From e5692871fb1ab126298437ac66efdc29f8482216 Mon Sep 17 00:00:00 2001
From: Ravi <6005951+thalurur@users.noreply.github.com>
Date: Mon, 3 May 2021 09:42:43 -0700
Subject: [PATCH 34/93] Get Transforms Page, Edit Transform page, Transform
Details page (#168)
---
models/interfaces.ts | 46 ++
public/index_management_app.tsx | 5 +-
public/models/interfaces.ts | 3 +-
public/pages/Main/Main.tsx | 56 ++-
.../ConfigureTransform/Configure.tsx | 57 +++
.../components/ConfigureTransform/index.ts | 18 +
.../components/DeleteModal/DeleteModal.tsx | 67 +++
.../components/DeleteModal/index.ts | 18 +
.../GeneralInformation/GeneralInformation.tsx | 88 ++++
.../components/GeneralInformation/index.ts | 18 +
.../components/Schedule/Schedule.tsx | 163 ++++++
.../Transforms/components/Schedule/index.ts | 18 +
.../TransformEmptyPrompt.tsx | 71 +++
.../components/TransformEmptyPrompt/index.ts | 18 +
.../TransformStatus/TransformStatus.tsx | 92 ++++
.../components/TransformStatus/index.ts | 18 +
.../containers/Transforms/EditTransform.tsx | 292 +++++++++++
.../Transforms/TransformDetails.tsx | 444 ++++++++++++++++
.../Transforms/TransformSettings.tsx | 59 +++
.../containers/Transforms/Transforms.tsx | 476 ++++++++++++++++++
.../Transforms/containers/Transforms/index.ts | 20 +
public/pages/Transforms/index.ts | 18 +
public/pages/Transforms/models/interfaces.ts | 25 +
public/pages/Transforms/utils/constants.tsx | 51 ++
public/pages/Transforms/utils/helpers.ts | 39 ++
.../pages/Transforms/utils/metadataHelper.tsx | 85 ++++
public/services/TransformService.ts | 71 +++
public/services/index.ts | 5 +-
public/utils/constants.ts | 8 +
server/clusters/ism/ismPlugin.ts | 85 ++++
server/models/interfaces.ts | 41 +-
server/plugin.ts | 8 +-
server/routes/index.ts | 3 +-
server/routes/rollups.ts | 2 +-
server/routes/transforms.ts | 104 ++++
server/services/TransformService.ts | 306 +++++++++++
server/services/index.ts | 3 +-
server/utils/constants.ts | 2 +
utils/constants.ts | 1 +
39 files changed, 2887 insertions(+), 17 deletions(-)
create mode 100644 public/pages/Transforms/components/ConfigureTransform/Configure.tsx
create mode 100644 public/pages/Transforms/components/ConfigureTransform/index.ts
create mode 100644 public/pages/Transforms/components/DeleteModal/DeleteModal.tsx
create mode 100644 public/pages/Transforms/components/DeleteModal/index.ts
create mode 100644 public/pages/Transforms/components/GeneralInformation/GeneralInformation.tsx
create mode 100644 public/pages/Transforms/components/GeneralInformation/index.ts
create mode 100644 public/pages/Transforms/components/Schedule/Schedule.tsx
create mode 100644 public/pages/Transforms/components/Schedule/index.ts
create mode 100644 public/pages/Transforms/components/TransformEmptyPrompt/TransformEmptyPrompt.tsx
create mode 100644 public/pages/Transforms/components/TransformEmptyPrompt/index.ts
create mode 100644 public/pages/Transforms/components/TransformStatus/TransformStatus.tsx
create mode 100644 public/pages/Transforms/components/TransformStatus/index.ts
create mode 100644 public/pages/Transforms/containers/Transforms/EditTransform.tsx
create mode 100644 public/pages/Transforms/containers/Transforms/TransformDetails.tsx
create mode 100644 public/pages/Transforms/containers/Transforms/TransformSettings.tsx
create mode 100644 public/pages/Transforms/containers/Transforms/Transforms.tsx
create mode 100644 public/pages/Transforms/containers/Transforms/index.ts
create mode 100644 public/pages/Transforms/index.ts
create mode 100644 public/pages/Transforms/models/interfaces.ts
create mode 100644 public/pages/Transforms/utils/constants.tsx
create mode 100644 public/pages/Transforms/utils/helpers.ts
create mode 100644 public/pages/Transforms/utils/metadataHelper.tsx
create mode 100644 public/services/TransformService.ts
create mode 100644 server/routes/transforms.ts
create mode 100644 server/services/TransformService.ts
diff --git a/models/interfaces.ts b/models/interfaces.ts
index c8e4860a6..aff6065b9 100644
--- a/models/interfaces.ts
+++ b/models/interfaces.ts
@@ -67,6 +67,14 @@ export interface DocumentRollup {
metadata: any;
}
+export interface DocumentTransform {
+ _id: string;
+ _seqNo: number;
+ _primaryTerm: number;
+ transform: Transform;
+ metadata: any;
+}
+
// TODO: Fill out when needed
// TODO: separate a frontend Policy from backendPolicy
export interface Policy {
@@ -125,6 +133,44 @@ export interface RollupMetadata {
};
}
+export interface Transform {
+ description: string;
+ groups: RollupDimensionItem[];
+ enabled: boolean;
+ enabled_at: number | null;
+ updated_at: number;
+ metadata_id: string | null;
+ aggregations: Map;
+ page_size: number;
+ schedule: IntervalSchedule | CronSchedule;
+ schema_version: number;
+ source_index: string;
+ target_index: string;
+ roles: String[];
+ data_selection_query: Map;
+}
+
+export interface TransformMetadata {
+ metadata_id: string;
+ transform_metadata: {
+ id: string;
+ seq_no: number;
+ primary_term: number;
+ transform_id: string;
+ after_key: Map | null;
+ last_updated_at: number;
+ status: string;
+ failure_reason: string | null;
+ stats: {
+ pages_processed: number | null;
+ documents_processed: number | null;
+ documents_indexed: number | null;
+ index_time_in_millis: number | null;
+ search_time_in_millis: number | null;
+ }
+ }
+}
+
export interface IntervalSchedule {
interval: {
startTime: number | null;
diff --git a/public/index_management_app.tsx b/public/index_management_app.tsx
index ac4d71bce..d60dc5ffa 100644
--- a/public/index_management_app.tsx
+++ b/public/index_management_app.tsx
@@ -18,7 +18,7 @@ import React from "react";
import ReactDOM from "react-dom";
import { render, unmountComponentAtNode } from "react-dom";
import { HashRouter as Router, Route } from "react-router-dom";
-import { IndexService, ManagedIndexService, PolicyService, RollupService, ServicesContext } from "./services";
+import { IndexService, ManagedIndexService, PolicyService, RollupService, TransformService, ServicesContext } from "./services";
import { DarkModeContext } from "./components/DarkMode";
import Main from "./pages/Main";
import { CoreServicesContext } from "./components/core_services";
@@ -30,7 +30,8 @@ export function renderApp(coreStart: CoreStart, params: AppMountParameters) {
const managedIndexService = new ManagedIndexService(http);
const policyService = new PolicyService(http);
const rollupService = new RollupService(http);
- const services = { indexService, managedIndexService, policyService, rollupService };
+ const transformService = new TransformService(http);
+ const services = { indexService, managedIndexService, policyService, rollupService, transformService };
const isDarkMode = coreStart.uiSettings.get("theme:darkMode") || false;
diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts
index 4fbade055..83d84d071 100644
--- a/public/models/interfaces.ts
+++ b/public/models/interfaces.ts
@@ -13,11 +13,12 @@
* permissions and limitations under the License.
*/
-import { IndexService, ManagedIndexService, PolicyService, RollupService } from "../services";
+import { IndexService, ManagedIndexService, PolicyService, RollupService, TransformService } from "../services";
export interface BrowserServices {
indexService: IndexService;
managedIndexService: ManagedIndexService;
policyService: PolicyService;
rollupService: RollupService;
+ transformService: TransformService;
}
diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx
index f930f2cb9..c48320e83 100644
--- a/public/pages/Main/Main.tsx
+++ b/public/pages/Main/Main.tsx
@@ -32,6 +32,8 @@ import { CoreServicesConsumer } from "../../components/core_services";
import CreateRollupForm from "../CreateRollup/containers/CreateRollupForm";
import EditRollup from "../EditRollup/containers";
import RollupDetails from "../RollupDetails/containers/RollupDetails";
+import { EditTransform, Transforms } from "../Transforms";
+import TransformDetails from "../Transforms/containers/Transforms/TransformDetails";
enum Navigation {
IndexManagement = "Index Management",
@@ -39,6 +41,7 @@ enum Navigation {
ManagedIndices = "Managed Indices",
Indices = "Indices",
Rollups = "Rollup Jobs",
+ Transforms = "Transform Jobs",
}
enum Pathname {
@@ -46,6 +49,7 @@ enum Pathname {
ManagedIndices = "/managed-indices",
Indices = "/indices",
Rollups = "/rollups",
+ Transforms = "/transforms",
}
interface MainProps extends RouteComponentProps {}
@@ -85,6 +89,12 @@ export default class Main extends Component {
href: `#${Pathname.Rollups}`,
isSelected: pathname === Pathname.Rollups,
},
+ {
+ name: Navigation.Transforms,
+ id: 5,
+ href: `#${Pathname.Transforms}`,
+ isSelected: pathname === Pathname.Transforms,
+ },
],
},
];
@@ -99,11 +109,15 @@ export default class Main extends Component {
{/*Hide side navigation bar when creating or editing rollup job*/}
- {pathname != ROUTES.CREATE_ROLLUP && pathname != ROUTES.EDIT_ROLLUP && pathname != ROUTES.ROLLUP_DETAILS && (
-
-
-
- )}
+ {pathname != ROUTES.CREATE_ROLLUP &&
+ pathname != ROUTES.EDIT_ROLLUP &&
+ pathname != ROUTES.ROLLUP_DETAILS &&
+ pathname != ROUTES.EDIT_TRANSFORM &&
+ pathname != ROUTES.TRANSFORM_DETAILS && (
+
+
+
+ )}
{
)}
/>
+ (
+
+
+
+ )}
+ />
+ (
+
+
+
+ )}
+ />
+ (
+
+
+
+ )}
+ />
+ (
+
+
+
+ )}
+ />
diff --git a/public/pages/Transforms/components/ConfigureTransform/Configure.tsx b/public/pages/Transforms/components/ConfigureTransform/Configure.tsx
new file mode 100644
index 000000000..c800cb5a1
--- /dev/null
+++ b/public/pages/Transforms/components/ConfigureTransform/Configure.tsx
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent } from "react";
+import { EuiSpacer, EuiFormRow, EuiFieldText, EuiTextArea, EuiText, EuiFlexGroup, EuiFlexItem } from "@elastic/eui";
+import { ContentPanel } from "../../../../components/ContentPanel";
+
+interface ConfigureTransformProps {
+ inEdit: boolean;
+ transformId: string;
+ error: string;
+ onChangeName: (value: ChangeEvent) => void;
+ onChangeDescription: (value: ChangeEvent) => void;
+ description: string;
+}
+
+const ConfigureTransform = ({ inEdit, transformId, error, onChangeName, onChangeDescription, description }: ConfigureTransformProps) => (
+
+
+
+
+
+
+
+
+
+
+ Description
+
+
+
+
+ - optional
+
+
+
+
+
+
+
+
+
+);
+
+export default ConfigureTransform;
diff --git a/public/pages/Transforms/components/ConfigureTransform/index.ts b/public/pages/Transforms/components/ConfigureTransform/index.ts
new file mode 100644
index 000000000..fd7952e60
--- /dev/null
+++ b/public/pages/Transforms/components/ConfigureTransform/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import ConfigureTransform from "./Configure";
+
+export default ConfigureTransform;
diff --git a/public/pages/Transforms/components/DeleteModal/DeleteModal.tsx b/public/pages/Transforms/components/DeleteModal/DeleteModal.tsx
new file mode 100644
index 000000000..1d6539e15
--- /dev/null
+++ b/public/pages/Transforms/components/DeleteModal/DeleteModal.tsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component, Fragment } from "react";
+import { EuiConfirmModal, EuiForm, EuiFormRow, EuiFieldText, EuiOverlayMask, EuiSpacer } from "@elastic/eui";
+
+// TODO: Merge with Rollup to create generic component
+interface DeleteModalProps {
+ item: string;
+ closeDeleteModal: (event?: any) => void;
+ onClickDelete: (event?: any) => void;
+}
+
+interface DeleteModalState {
+ confirmDeleteText: string;
+}
+
+export default class DeleteModal extends Component {
+ state = { confirmDeleteText: "" };
+
+ onChange = (e: ChangeEvent): void => {
+ this.setState({ confirmDeleteText: e.target.value });
+ };
+
+ render() {
+ const { item, closeDeleteModal, onClickDelete } = this.props;
+ const { confirmDeleteText } = this.state;
+
+ return (
+
+
+
+
+ By deleting "{item}", all future scheduled executions will be canceled. However, your target index
+ and data in it will remain intact.
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/Transforms/components/DeleteModal/index.ts b/public/pages/Transforms/components/DeleteModal/index.ts
new file mode 100644
index 000000000..0d2c98428
--- /dev/null
+++ b/public/pages/Transforms/components/DeleteModal/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import DeleteModal from "./DeleteModal";
+
+export default DeleteModal;
diff --git a/public/pages/Transforms/components/GeneralInformation/GeneralInformation.tsx b/public/pages/Transforms/components/GeneralInformation/GeneralInformation.tsx
new file mode 100644
index 000000000..5742547f3
--- /dev/null
+++ b/public/pages/Transforms/components/GeneralInformation/GeneralInformation.tsx
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component } from "react";
+import { EuiFlexGrid, EuiSpacer, EuiFlexItem, EuiText } from "@elastic/eui";
+import { ContentPanel, ContentPanelActions } from "../../../../components/ContentPanel";
+import { ModalConsumer } from "../../../../components/Modal";
+
+interface GeneralInformationProps {
+ id: string;
+ description: string;
+ sourceIndex: string;
+ targetIndex: string;
+ scheduledText: string;
+ pageSize: number;
+ updatedAt: number;
+ onEdit: () => void;
+}
+
+export default class GenerationInformation extends Component {
+ constructor(props: GeneralInformationProps) {
+ super(props);
+ }
+
+ render() {
+ const { id, description, sourceIndex, targetIndex, scheduledText, pageSize, updatedAt, onEdit } = this.props;
+ const infoItems = [
+ { term: "Name", value: id },
+ { term: "Source index", value: sourceIndex },
+ { term: "Target index", value: targetIndex },
+ { term: "Schedule", value: scheduledText },
+ { term: "Description", value: description || "-" },
+ { term: "UpdatedAt", value: updatedAt },
+ { term: "Pages per execution", value: pageSize },
+ ];
+
+ return (
+
+ {() => (
+ onEdit(),
+ },
+ },
+ ]}
+ />
+ )}
+
+ }
+ bodyStyles={{ padding: "initial" }}
+ title="General information"
+ titleSize="m"
+ >
+
+
+
+ {infoItems.map((item, index) => (
+
+
+ {item.term}
+ {item.value}
+
+
+ ))}
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/Transforms/components/GeneralInformation/index.ts b/public/pages/Transforms/components/GeneralInformation/index.ts
new file mode 100644
index 000000000..dd311c3f6
--- /dev/null
+++ b/public/pages/Transforms/components/GeneralInformation/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import GeneralInformation from "./GeneralInformation";
+
+export default GeneralInformation;
diff --git a/public/pages/Transforms/components/Schedule/Schedule.tsx b/public/pages/Transforms/components/Schedule/Schedule.tsx
new file mode 100644
index 000000000..d16cc0853
--- /dev/null
+++ b/public/pages/Transforms/components/Schedule/Schedule.tsx
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { ChangeEvent, Component } from "react";
+import moment from "moment-timezone";
+import {
+ EuiSpacer,
+ EuiCheckbox,
+ EuiAccordion,
+ EuiFormRow,
+ EuiSelect,
+ EuiFieldNumber,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiTextArea,
+} from "@elastic/eui";
+// @ts-ignore
+import { htmlIdGenerator } from "@elastic/eui/lib/services";
+import { ScheduleIntervalTimeunitOptions } from "../../utils/constants";
+import { ContentPanel } from "../../../../components/ContentPanel";
+
+interface ScheduleProps {
+ transformId: string;
+ error: string;
+ enabled: boolean;
+ pageSize: number;
+ onEnabledChange: () => void;
+ schedule: string;
+ interval: number;
+ intervalTimeUnit: string;
+ intervalError: string;
+ cronExpression: string;
+ cronTimeZone: string;
+ onPageChange: (e: ChangeEvent) => void;
+ onScheduleChange: (e: ChangeEvent) => void;
+ onCronExpressionChange: (e: ChangeEvent) => void;
+ onCronTimeZoneChange: (e: ChangeEvent) => void;
+ onIntervalChange: (e: ChangeEvent) => void;
+ onIntervalTimeUnitChange: (e: ChangeEvent) => void;
+}
+
+const selectInterval = (
+ interval: number,
+ intervalTimeunit: string,
+ intervalError: string,
+ onIntervalChange: (e: ChangeEvent) => void,
+ onIntervalTimeUnitChange: (value: ChangeEvent) => void
+) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+const timezones = moment.tz.names().map((tz) => ({ label: tz, text: tz }));
+
+// TODO: Check wording for page size form with UX team
+export default class Schedule extends Component {
+ constructor(props: ScheduleProps) {
+ super(props);
+ }
+
+ render() {
+ const {
+ enabled,
+ pageSize,
+ onEnabledChange,
+ onPageChange,
+ schedule,
+ onScheduleChange,
+ interval,
+ intervalError,
+ intervalTimeUnit,
+ onIntervalChange,
+ onIntervalTimeUnitChange,
+ cronExpression,
+ cronTimeZone,
+ onCronExpressionChange,
+ onCronTimeZoneChange,
+ } = this.props;
+ return (
+
+
+
+
+
+
+
+
+
+
+ {schedule == "fixed" ? (
+ selectInterval(interval, intervalTimeUnit, intervalError, onIntervalChange, onIntervalTimeUnitChange)
+ ) : (
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/Transforms/components/Schedule/index.ts b/public/pages/Transforms/components/Schedule/index.ts
new file mode 100644
index 000000000..f18d83cb2
--- /dev/null
+++ b/public/pages/Transforms/components/Schedule/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import Schedule from "./Schedule";
+
+export default Schedule;
diff --git a/public/pages/Transforms/components/TransformEmptyPrompt/TransformEmptyPrompt.tsx b/public/pages/Transforms/components/TransformEmptyPrompt/TransformEmptyPrompt.tsx
new file mode 100644
index 000000000..9a4868e1d
--- /dev/null
+++ b/public/pages/Transforms/components/TransformEmptyPrompt/TransformEmptyPrompt.tsx
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React from "react";
+import { EuiButton, EuiEmptyPrompt, EuiText } from "@elastic/eui";
+import {PLUGIN_NAME, ROUTES} from "../../../../utils/constants";
+
+interface TransformEmptyPromptProps {
+ filterIsApplied: boolean;
+ loading: boolean;
+ resetFilters: () => void;
+}
+
+export const TEXT = {
+ RESET_FILTERS: "There are no transform jobs matching your applied filters. Reset your filters to view your transform jobs.",
+ NO_TRANSFORMS:
+ "Transform jobs help you create a materialized view on top of existing data.",
+ LOADING: "Loading transform jobs...",
+};
+
+const getMessagePrompt = ({ filterIsApplied, loading }: TransformEmptyPromptProps) => {
+ if (loading) return TEXT.LOADING;
+ if (filterIsApplied) return TEXT.RESET_FILTERS;
+ return TEXT.NO_TRANSFORMS;
+};
+
+const getActions: React.SFC = ({ filterIsApplied, loading, resetFilters }) => {
+ if (loading) {
+ return null;
+ }
+
+ if (filterIsApplied) {
+ return (
+
+ Reset Filters
+
+ );
+ }
+
+ return (
+
+ Create transform
+
+ );
+};
+
+const TransformEmptyPrompt: React.SFC = (props) => (
+
+ {getMessagePrompt(props)}
+
+ }
+ actions={getActions(props)}
+ />
+);
+
+export default TransformEmptyPrompt;
diff --git a/public/pages/Transforms/components/TransformEmptyPrompt/index.ts b/public/pages/Transforms/components/TransformEmptyPrompt/index.ts
new file mode 100644
index 000000000..7a5714db4
--- /dev/null
+++ b/public/pages/Transforms/components/TransformEmptyPrompt/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import TransformEmptyPrompt from "./TransformEmptyPrompt";
+
+export default TransformEmptyPrompt;
diff --git a/public/pages/Transforms/components/TransformStatus/TransformStatus.tsx b/public/pages/Transforms/components/TransformStatus/TransformStatus.tsx
new file mode 100644
index 000000000..a091160de
--- /dev/null
+++ b/public/pages/Transforms/components/TransformStatus/TransformStatus.tsx
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component } from "react";
+import { EuiFlexGrid, EuiSpacer, EuiFlexItem, EuiText } from "@elastic/eui";
+import { TransformMetadata } from "../../../../../models/interfaces";
+import { ContentPanel } from "../../../../components/ContentPanel";
+import { renderStatus } from "../../utils/metadataHelper";
+
+interface TransformStatusProps {
+ metadata: TransformMetadata | undefined;
+}
+
+export default class TransformStatus extends Component {
+ constructor(props: TransformStatusProps) {
+ super(props);
+ }
+
+ render() {
+ const { metadata } = this.props;
+ return (
+
+
+
+
+
+
+ Status
+ {renderStatus(metadata)}
+
+
+
+
+ Documents indexed
+
+ {metadata == null || metadata.transform_metadata == null ? "-" : metadata.transform_metadata.stats.documents_indexed}
+
+
+
+
+
+ Indexed time (ms)
+
+ {metadata == null || metadata.transform_metadata == null ? "-" : metadata.transform_metadata.stats.index_time_in_millis}
+
+
+
+
+
+
+
+ Document processed
+
+ {metadata == null || metadata.transform_metadata == null ? "-" : metadata.transform_metadata.stats.documents_processed}
+
+
+
+
+
+ Search time (ms)
+
+ {metadata == null || metadata.transform_metadata == null ? "-" : metadata.transform_metadata.stats.search_time_in_millis}
+
+
+
+
+
+
+
+ Page processed
+ {metadata == null || metadata.transform_metadata == null ? "-" : metadata.transform_metadata.stats.pages_processed}
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/public/pages/Transforms/components/TransformStatus/index.ts b/public/pages/Transforms/components/TransformStatus/index.ts
new file mode 100644
index 000000000..8f2f8df70
--- /dev/null
+++ b/public/pages/Transforms/components/TransformStatus/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import TransformStatus from "./TransformStatus";
+
+export default TransformStatus;
diff --git a/public/pages/Transforms/containers/Transforms/EditTransform.tsx b/public/pages/Transforms/containers/Transforms/EditTransform.tsx
new file mode 100644
index 000000000..9ba1eb804
--- /dev/null
+++ b/public/pages/Transforms/containers/Transforms/EditTransform.tsx
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import { RouteComponentProps } from "react-router-dom";
+import { TransformService } from "../../../../services";
+import { CoreServicesContext } from "../../../../components/core_services";
+import React, { ChangeEvent, Component } from "react";
+import { EMPTY_TRANSFORM } from "../../utils/constants";
+import queryString from "query-string";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import { getErrorMessage } from "../../../../utils/helpers";
+import { EuiFlexItem, EuiFlexGroup, EuiButton, EuiTitle, EuiSpacer, EuiButtonEmpty } from "@elastic/eui";
+import ConfigureTransform from "../../components/ConfigureTransform";
+import Schedule from "../../components/Schedule";
+import moment from "moment";
+import { Transform } from "../../../../../models/interfaces";
+
+interface EditTransformProps extends RouteComponentProps {
+ transformService: TransformService;
+}
+
+interface EditTransformState {
+ id: string;
+ error: string;
+ seqNo: number | null;
+ primaryTerm: number | null;
+ description: string;
+ pageSize: number;
+ enabled: boolean;
+ transformJSON: any;
+ submitError: string;
+ isSubmitting: boolean;
+ hasSubmitted: boolean;
+ interval: number;
+ intervalError: string;
+ intervalTimeUnit: string;
+ cronExpression: string;
+ cronTimeZone: string;
+ schedule: string;
+}
+
+export default class EditTransform extends Component {
+ static contextType = CoreServicesContext;
+
+ constructor(props: EditTransformProps) {
+ super(props);
+ this.state = {
+ id: "",
+ error: "",
+ seqNo: null,
+ primaryTerm: null,
+ description: "",
+ pageSize: 1000,
+ enabled: true,
+ transformJSON: EMPTY_TRANSFORM,
+ isSubmitting: false,
+ interval: 2,
+ intervalError: "",
+ intervalTimeUnit: "MINUTES",
+ cronExpression: "",
+ cronTimeZone: "UTC",
+ schedule: "fixed",
+ submitError: "",
+ hasSubmitted: false,
+ };
+ }
+
+ componentDidMount = async () => {
+ const { id } = queryString.parse(this.props.location.search);
+ if (typeof id === "string" && !!id) {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS, BREADCRUMBS.EDIT_TRANSFORM, { text: id }]);
+ await this.getTransform(id);
+ } else {
+ this.context.notifications.toasts.addDanger(`Invalid transform id: ${id}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ };
+
+ getTransform = async (transformId: string) => {
+ try {
+ const { transformService } = this.props;
+ const response = await transformService.getTransform(transformId);
+
+ if (response.ok) {
+ let json = JSON.parse(this.state.transformJSON);
+ json.transform = response.response.transform;
+
+ this.setState({
+ seqNo: response.response._seqNo,
+ primaryTerm: response.response._primaryTerm,
+ id: response.response._id,
+ description: response.response.transform.description,
+ enabled: response.response.transform.enabled,
+ pageSize: response.response.transform.page_size,
+ transformJSON: json,
+ });
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not load transform job ${transformId}: ${response.error}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, `Could not load transform job ${transformId}`));
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ };
+
+ render() {
+ const {
+ id,
+ error,
+ pageSize,
+ description,
+ isSubmitting,
+ enabled,
+ interval,
+ intervalError,
+ intervalTimeUnit,
+ cronExpression,
+ cronTimeZone,
+ schedule,
+ } = this.state;
+ return (
+
+
+ Edit transform job
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Save changes
+
+
+
+
+ );
+ }
+
+ onCancel = () => {
+ this.props.history.push(ROUTES.TRANSFORMS);
+ };
+
+ onSubmit = async (): Promise => {
+ const { id, transformJSON } = this.state;
+ this.setState({ submitError: "", isSubmitting: true, hasSubmitted: true });
+ try {
+ this.updateSchedule();
+ await this.update(id, transformJSON);
+ } catch (err) {
+ this.context.notifications.toasts.addDanger("Invalid Transform JSON");
+ console.error(err);
+ }
+ };
+
+ updateSchedule = () => {
+ const { schedule, cronExpression, cronTimeZone, interval, intervalTimeUnit } = this.state;
+ let json = this.state.transformJSON;
+ if (schedule == "cron") {
+ json.transform.schedule.cron = { expression: `${cronExpression}`, timezone: `${cronTimeZone}` };
+ delete json.transform.schedule["interval"];
+ } else {
+ json.transform.schedule.interval = {
+ start_time: moment().unix(),
+ unit: intervalTimeUnit,
+ period: interval,
+ };
+ delete json.transform.schedule["cron"];
+ }
+
+ this.setState({ transformJSON: json });
+ };
+
+ update = async (transformId: string, transform: Transform): Promise => {
+ try {
+ const { transformService } = this.props;
+ const { primaryTerm, seqNo } = this.state;
+ if (primaryTerm == null || seqNo == null) {
+ this.context.notifications.toasts.addDanger("Could not update transform without seqNo and primaryTerm");
+ return;
+ }
+ const response = await transformService.putTransform(transform, transformId, seqNo, primaryTerm);
+ if (response.ok) {
+ this.context.notifications.toasts.addSuccess(`Changes to transform saved`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ } else {
+ this.context.notifications.toasts.addDanger(`Couldn't update transform ${transformId}: ${response.error}`);
+ this.setState({ submitError: response.error });
+ }
+ } catch (err) {
+ this.setState({ submitError: getErrorMessage(err, `Couldn't update transform ${transformId}`) });
+ }
+ };
+
+ onNameChange = (e: ChangeEvent) => {
+ // DO NOTHING SINCE edit is disabled for this page
+ };
+
+ onDescriptionChange = (e: ChangeEvent) => {
+ const description = e.target.value;
+ let json = this.state.transformJSON;
+ json.transform.description = description;
+ this.setState({ transformJSON: json, description: description });
+ };
+
+ onEnabledChange = () => {
+ const enabled = this.state.enabled;
+ let json = this.state.transformJSON;
+ json.transform.enabled = enabled;
+ this.setState({ transformJSON: json, enabled: !enabled });
+ };
+
+ onCronExpressionChange = (e: ChangeEvent) => {
+ this.setState({ cronExpression: e.target.value });
+ };
+
+ onCronTimeZoneChange = (e: ChangeEvent) => {
+ this.setState({ cronTimeZone: e.target.value });
+ };
+
+ onIntervalChange = (e: ChangeEvent) => {
+ this.setState({ interval: e.target.valueAsNumber });
+ if (e.target.value == "") {
+ const intervalError = "Interval value is required.";
+ this.setState({ intervalError: intervalError });
+ } else {
+ this.setState({ intervalError: "" });
+ }
+ };
+
+ onPageChange = (e: ChangeEvent) => {
+ const pageSize = e.target.valueAsNumber;
+ let json = this.state.transformJSON;
+ json.transform.pageSize = pageSize;
+ this.setState({ pageSize: pageSize, transformJSON: json });
+ };
+
+ onScheduleChange = (e: ChangeEvent) => {
+ this.setState({ schedule: e.target.value });
+ };
+
+ onIntervalTimeUnitChange = (e: ChangeEvent) => {
+ this.setState({ intervalTimeUnit: e.target.value });
+ };
+}
diff --git a/public/pages/Transforms/containers/Transforms/TransformDetails.tsx b/public/pages/Transforms/containers/Transforms/TransformDetails.tsx
new file mode 100644
index 000000000..b4e60e3e1
--- /dev/null
+++ b/public/pages/Transforms/containers/Transforms/TransformDetails.tsx
@@ -0,0 +1,444 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import {
+ EuiSpacer,
+ EuiTitle,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButton,
+ EuiOverlayMask,
+ EuiButtonEmpty,
+ EuiModalFooter,
+ EuiModal,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiModalBody,
+ EuiCodeBlock,
+ EuiHealth,
+ EuiContextMenuItem,
+ EuiContextMenuPanel,
+ EuiTextColor,
+ EuiPopover,
+} from "@elastic/eui";
+import { TransformService } from "../../../../services";
+import { RouteComponentProps } from "react-router-dom";
+import React, { Component } from "react";
+import { CoreServicesContext } from "../../../../components/core_services";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import queryString from "query-string";
+import { getErrorMessage } from "../../../../utils/helpers";
+import { DimensionItem, MetricItem, RollupDimensionItem, TransformMetadata } from "../../../../../models/interfaces";
+import DeleteModal from "../../components/DeleteModal";
+import GenerationInformation from "../../components/GeneralInformation";
+import TransformStatus from "../../components/TransformStatus";
+import { EMPTY_TRANSFORM } from "../../utils/constants";
+import TransformSettings from "./TransformSettings";
+
+interface TransformDetailsProps extends RouteComponentProps {
+ transformService: TransformService;
+}
+
+interface TransformDetailsState {
+ id: string;
+ description: string;
+ enabled: boolean;
+ enabledAt: number | null;
+ updatedAt: number;
+ pageSize: number;
+ transformJson: any;
+ sourceIndex: string;
+ targetIndex: string;
+ aggregationsShown: MetricItem[];
+ groupsShown: DimensionItem[];
+ metadata: TransformMetadata | undefined;
+ interval: number;
+ intervalTimeUnit: string;
+ cronExpression: string;
+ isModalOpen: boolean;
+ isDeleteModalOpen: boolean;
+ isPopOverOpen: boolean;
+}
+
+export default class TransformDetails extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: TransformDetailsProps) {
+ super(props);
+
+ this.state = {
+ id: "",
+ description: "",
+ enabled: false,
+ enabledAt: null,
+ updatedAt: 1,
+ pageSize: 1000,
+ transformJson: EMPTY_TRANSFORM,
+ sourceIndex: "",
+ targetIndex: "",
+ aggregationsShown: [],
+ groupsShown: [],
+ metadata: undefined,
+ interval: 2,
+ intervalTimeUnit: "",
+ cronExpression: "",
+ isModalOpen: false,
+ isDeleteModalOpen: false,
+ isPopOverOpen: false,
+ };
+ }
+
+ componentDidMount = async (): Promise => {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS]);
+ const { id } = queryString.parse(this.props.location.search);
+ if (typeof id === "string") {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS, { text: id }]);
+ this.props.history.push(`${ROUTES.TRANSFORM_DETAILS}?id=${id}`);
+ await this.getTransform(id);
+ this.forceUpdate();
+ } else {
+ this.context.notifications.toasts.addDanger(`Invalid transform id: ${id}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ };
+
+ getTransform = async (transformId: string) => {
+ try {
+ const { transformService } = this.props;
+ const response = await transformService.getTransform(transformId);
+
+ if (response.ok) {
+ let json = response.response;
+ // let aggregations = this.parseAggregations(response.response.transform.aggregations);
+ let groups = this.parseGroups(response.response.transform.groups);
+ this.setState({
+ id: response.response._id,
+ description: response.response.transform.description,
+ enabled: response.response.transform.enabled,
+ enabledAt: response.response.transform.enabled_at,
+ updatedAt: response.response.transform.updated_at,
+ pageSize: response.response.transform.page_size,
+ transformJson: json,
+ sourceIndex: response.response.transform.source_index,
+ targetIndex: response.response.transform.target_index,
+ aggregationsShown: [],
+ groupsShown: groups.slice(0, 10),
+ });
+
+ if (response.response.metadata != null) {
+ this.setState({ metadata: response.response.metadata[response.response._id] });
+ }
+ if ("interval" in response.response.transform.schedule) {
+ this.setState({
+ interval: response.response.transform.schedule.interval.period,
+ intervalTimeUnit: response.response.transform.schedule.interval.unit,
+ });
+ } else {
+ this.setState({ cronExpression: response.response.transform.schedule.cron.expression });
+ }
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not load transform job ${transformId}: ${response.error}`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, `Could not load transform job ${transformId}`));
+ this.props.history.push(ROUTES.TRANSFORMS);
+ }
+ };
+
+ parseGroups = (groups: RollupDimensionItem[]): DimensionItem[] => {
+ const sourceArray = groups.slice(1, groups.length);
+ if (sourceArray.length == 0) return [];
+ // @ts-ignore
+ return sourceArray.map((group: RollupDimensionItem) => {
+ let sequence = groups.indexOf(group);
+ switch (true) {
+ case group.date_histogram != null:
+ return {
+ sequence: sequence,
+ aggregationMethod: "date_histogram",
+ field: {
+ label: group.date_histogram?.source_field,
+ },
+ interval: group.date_histogram?.interval,
+ };
+ case group.histogram != null:
+ return {
+ sequence: sequence,
+ aggregationMethod: "histogram",
+ field: {
+ label: group.histogram?.source_field,
+ },
+ interval: group.histogram?.interval,
+ };
+ case group.terms != null:
+ return {
+ sequence: sequence,
+ aggregationMethod: "terms",
+ field: {
+ label: group.terms?.source_field,
+ },
+ interval: null,
+ };
+ }
+ });
+ };
+
+ render() {
+ const {
+ id,
+ enabled,
+ updatedAt,
+ description,
+ sourceIndex,
+ targetIndex,
+ pageSize,
+ metadata,
+ transformJson,
+ isDeleteModalOpen,
+ isModalOpen,
+ isPopOverOpen,
+ } = this.state;
+
+ let scheduleText = "At some time";
+ const actionButton = (
+
+ Actions
+
+ );
+
+ const actionItems = [
+ {
+ this.closePopover();
+ this.onEnable();
+ }}
+ >
+ Enable job
+ ,
+ {
+ this.closePopover();
+ this.onDisable();
+ }}
+ >
+ Disable job
+ ,
+ {
+ this.closePopover();
+ this.showJsonModal();
+ }}
+ >
+ View JSON
+ ,
+ {
+ this.closePopover();
+ this.showDeleteModal();
+ }}
+ >
+ Duplicate job
+ ,
+ {
+ this.closePopover();
+ this.showDeleteModal();
+ }}
+ >
+ Delete
+ ,
+ ];
+
+ return (
+
+
+
+
+ {id}
+
+
+
+ {enabled ? {"Enabled on " + updatedAt} : Disabled}
+
+
+
+
+
+
+
+
+
+
+
+ View target index in Discover
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isModalOpen && (
+
+
+
+ {"View JSON of " + id}
+
+
+
+
+ {JSON.stringify(transformJson, null, 4)}
+
+
+
+
+ Close
+
+
+
+ )}
+
+ {isDeleteModalOpen && }
+
+ );
+ }
+
+ onClickDelete = async () => {
+ const { id } = this.state;
+ const { transformService } = this.props;
+ try {
+ const response = await transformService.deleteTransform(id);
+ if (response.ok) {
+ this.closeDeleteModal();
+ this.context.notification.toasts.addSuccess(`"${id}" successfully deleted!`);
+ this.props.history.push(ROUTES.TRANSFORMS);
+ } else {
+ this.context.notifications.toasts.addDanger(`could not delete transform job "${id}" : ${response.error}`);
+ }
+ } catch (err) {
+ this.context.notification.toasts.addDanger(getErrorMessage(err, "Could not delete the transform job"));
+ }
+ };
+
+ onEdit = () => {
+ const { id } = this.state;
+ this.props.history.push(`${ROUTES.EDIT_TRANSFORM}?id=${id}`);
+ };
+
+ onEnable = async () => {
+ const { id } = this.state;
+ const { transformService } = this.props;
+ try {
+ const response = await transformService.startTransform(id);
+ if (response.ok) {
+ this.setState({ enabled: true });
+ await this.getTransform(id);
+ this.forceUpdate();
+ this.context.notifications.toasts.addSuccess(`${id} is enabled`);
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not enable transform job "${id}": ${response.error}`);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, `Could not enable transform job ${id}`));
+ }
+ };
+
+ onDisable = async () => {
+ const { id } = this.state;
+ const { transformService } = this.props;
+ try {
+ const response = await transformService.stopTransform(id);
+ if (response.ok) {
+ this.setState({ enabled: false });
+ await this.getTransform(id);
+ this.forceUpdate();
+ this.context.notifications.toasts.addSuccess(`${id} is disabled`);
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not disable transform job "${id}": ${response.error}`);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, `Could not disable transform job ${id}`));
+ }
+ };
+
+ showJsonModal = () => {
+ this.setState({ isModalOpen: true });
+ };
+
+ closeModal = () => {
+ this.setState({ isModalOpen: false });
+ };
+
+ showDeleteModal = () => {
+ this.setState({ isDeleteModalOpen: true });
+ };
+
+ closeDeleteModal = () => {
+ this.setState({ isDeleteModalOpen: false });
+ };
+
+ onActionButtonClick = () => {
+ this.setState({ isPopOverOpen: !this.state.isPopOverOpen });
+ };
+
+ closePopover = () => {
+ this.setState({ isPopOverOpen: false });
+ };
+}
diff --git a/public/pages/Transforms/containers/Transforms/TransformSettings.tsx b/public/pages/Transforms/containers/Transforms/TransformSettings.tsx
new file mode 100644
index 000000000..99a804d16
--- /dev/null
+++ b/public/pages/Transforms/containers/Transforms/TransformSettings.tsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import React, { Component } from "react";
+import { EuiSpacer, EuiText, EuiAccordion } from "@elastic/eui";
+// @ts-ignore
+import { htmlIdGenerator } from "@elastic/eui/lib/services";
+import { ContentPanel } from "../../../../components/ContentPanel";
+import { TransformService } from "../../../../services";
+
+interface TransformSettingsProps {
+ transformService: TransformService;
+ transformJson: Map;
+}
+
+interface TransformSettingsState {}
+
+export default class TransformSettings extends Component {
+ constructor(props: TransformSettingsProps) {
+ super(props);
+ this.state = {};
+ }
+
+ render() {
+ return (
+
+
+
+
+ Groups
+
+
+
+
+ // TODO: Use the source data preview table from create workflow // TODO: Use the transformed preview table from create workflow
+
+
+
+ );
+ }
+
+ onClick = async () => {
+ const response = await this.props.transformService.previewTransform(this.props.transformJson);
+ console.log(response);
+ console.log("tada");
+ };
+}
diff --git a/public/pages/Transforms/containers/Transforms/Transforms.tsx b/public/pages/Transforms/containers/Transforms/Transforms.tsx
new file mode 100644
index 000000000..9057680da
--- /dev/null
+++ b/public/pages/Transforms/containers/Transforms/Transforms.tsx
@@ -0,0 +1,476 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import {
+ // @ts-ignore
+ Criteria,
+ Direction,
+ EuiPanel,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiTitle,
+ EuiButton,
+ EuiBasicTable,
+ EuiPopover,
+ EuiContextMenuPanel,
+ EuiFieldSearch,
+ EuiHorizontalRule,
+ EuiPagination,
+ EuiLink,
+ EuiTextColor,
+ EuiTableFieldDataColumnType,
+ EuiContextMenuItem,
+ // @ts-ignore
+ Pagination,
+ EuiTableSelectionType,
+ EuiTableSortingType,
+} from "@elastic/eui";
+import queryString from "query-string";
+import { RouteComponentProps } from "react-router-dom";
+import TransformService from "../../../../services/TransformService";
+import { DocumentTransform } from "../../../../../models/interfaces";
+import React, { Component } from "react";
+import { CoreServicesContext } from "../../../../components/core_services";
+import { getURLQueryParams, renderTime } from "../../utils/helpers";
+import { TransformQueryParams } from "../../models/interfaces";
+import { getErrorMessage } from "../../../../utils/helpers";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
+import DeleteModal from "../../components/DeleteModal";
+import TransformEmptyPrompt from "../../components/TransformEmptyPrompt";
+import { renderEnabled, renderStatus } from "../../utils/metadataHelper";
+import { DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_QUERY_PARAMS } from "../../../Indices/utils/constants";
+import _ from "lodash";
+import { ManagedCatIndex } from "../../../../../server/models/interfaces";
+
+interface TransformProps extends RouteComponentProps {
+ transformService: TransformService;
+}
+
+interface TransformState {
+ totalTransforms: number;
+ from: number;
+ size: number;
+ search: string;
+ sortField: keyof DocumentTransform;
+ sortDirection: Direction;
+ selectedItems: DocumentTransform[];
+ transforms: DocumentTransform[];
+ fetchingTransforms: boolean;
+ transformMetadata: {};
+ isPopOverOpen: boolean;
+ isDeleteModalVisible: boolean;
+}
+
+export default class Transforms extends Component {
+ static contextType = CoreServicesContext;
+ constructor(props: TransformProps) {
+ super(props);
+
+ const { from, size, search, sortField, sortDirection } = getURLQueryParams(this.props.location);
+
+ this.state = {
+ totalTransforms: 0,
+ from,
+ size,
+ search,
+ sortField,
+ sortDirection,
+ selectedItems: [],
+ transforms: [],
+ fetchingTransforms: true,
+ transformMetadata: {},
+ isPopOverOpen: false,
+ isDeleteModalVisible: false,
+ };
+
+ this.getTransforms = _.debounce(this.getTransforms, 500, { leading: true });
+ }
+
+ async componentDidMount() {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.INDEX_MANAGEMENT, BREADCRUMBS.TRANSFORMS]);
+ await this.getTransforms();
+ }
+
+ async componentDidUpdate(prevProps: TransformProps, prevState: TransformState) {
+ const prevQuery = Transforms.getQueryObjectFromState(prevState);
+ const currQuery = Transforms.getQueryObjectFromState(this.state);
+ if (!_.isEqual(prevQuery, currQuery)) {
+ await this.getTransforms();
+ }
+ }
+
+ render() {
+ const {
+ totalTransforms,
+ from,
+ size,
+ search,
+ sortField,
+ sortDirection,
+ selectedItems,
+ transforms,
+ fetchingTransforms,
+ isPopOverOpen,
+ isDeleteModalVisible,
+ } = this.state;
+
+ const filterIsApplied = !!search;
+ const pageCount = Math.ceil(totalTransforms / size) || 1;
+ const page = Math.floor(from / size);
+ const pagination: Pagination = {
+ pageIndex: page,
+ pageSize: size,
+ pageSizeOptions: DEFAULT_PAGE_SIZE_OPTIONS,
+ totalItemCount: totalTransforms,
+ };
+
+ const columns: EuiTableFieldDataColumnType[] = [
+ {
+ field: "_id",
+ name: "Name",
+ sortable: true,
+ textOnly: true,
+ truncateText: true,
+ render: (_id) => (
+ this.props.history.push(`${ROUTES.TRANSFORM_DETAILS}?id=${_id}`)} data-test-subj={`transformLink_${_id}`}>
+ {_id}
+
+ ),
+ },
+ {
+ field: "transform.source_index",
+ name: "Source index",
+ sortable: true,
+ textOnly: true,
+ truncateText: true,
+ },
+ {
+ field: "transform.target_index",
+ name: "Target index",
+ sortable: true,
+ textOnly: true,
+ truncateText: true,
+ },
+ {
+ field: "transform.enabled",
+ name: "Job state",
+ sortable: true,
+ textOnly: true,
+ truncateText: true,
+ render: renderEnabled,
+ },
+ {
+ field: "transform.updated_at",
+ name: "Last updated time",
+ sortable: true,
+ textOnly: true,
+ render: (updated_at) => renderTime(updated_at),
+ },
+ {
+ field: "metadata",
+ name: "transform job status",
+ sortable: false,
+ textOnly: true,
+ render: (metadata) => renderStatus(metadata),
+ },
+ ];
+
+ const actionButton = (
+
+ Actions
+
+ );
+
+ const actionItems = [
+ {
+ this.closePopover();
+ this.onClickEdit();
+ }}
+ >
+ Edit
+ ,
+ {
+ this.closePopover();
+ this.showDeleteModal();
+ }}
+ >
+ Delete
+ ,
+ ];
+
+ const selection: EuiTableSelectionType = {
+ onSelectionChange: this.onSelectionChange,
+ };
+
+ const sorting: EuiTableSortingType = {
+ sort: {
+ direction: sortDirection,
+ field: sortField,
+ },
+ };
+
+ return (
+
+
+
+
+ {"Transform jobs (" + `${transforms.length}` + ")"}
+
+
+
+
+
+
+ Disable
+
+
+
+ {
+ this.onEnable();
+ }}
+ data-test-subj="enableButton"
+ >
+ Enable
+
+
+
+
+
+
+
+
+
+ Create transform job
+
+
+
+
+
+
+
+
+
+
+
+ {pageCount > 1 && (
+
+
+
+ )}
+
+
+
+
+
+ }
+ onChange={this.onTableChange}
+ pagination={pagination}
+ selection={selection}
+ sorting={sorting}
+ tableLayout="auto"
+ />
+ {isDeleteModalVisible && (
+
+ )}
+
+
+ );
+ }
+
+ getTransforms = async () => {
+ this.setState({ fetchingTransforms: true });
+ try {
+ const { transformService, history } = this.props;
+ const queryObject = Transforms.getQueryObjectFromState(this.state);
+ const queryParamsString = queryString.stringify(Transforms.getQueryObjectFromState(this.state));
+ history.replace({ ...this.props.location, search: queryParamsString });
+ const response = await transformService.getTransforms(queryObject);
+ if (response.ok) {
+ const { transforms, totalTransforms, metadata } = response.response;
+ this.setState({ transforms, totalTransforms, transformMetadata: metadata });
+ } else {
+ this.context.notifications.toasts.addDanger(response.error);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, "There was problem loading transforms"));
+ }
+ this.setState({ fetchingTransforms: false });
+ };
+
+ getSelectedTransformIds = () => {
+ return "asd";
+ // this.state.selectedItems.map((item: DocumentTransform) => { return item._id }).join(", ");
+ };
+
+ onSelectionChange = (selectedItems: DocumentTransform[]): void => {
+ this.setState({ selectedItems });
+ };
+
+ showDeleteModal = () => {
+ this.setState({ isDeleteModalVisible: true });
+ };
+
+ closeDeleteModal = () => {
+ this.setState({ isDeleteModalVisible: false });
+ };
+
+ onClickCreate = () => {
+ this.props.history.push(ROUTES.CREATE_TRANSFORM);
+ };
+
+ onClickEdit = () => {
+ const {
+ selectedItems: [{ _id }],
+ } = this.state;
+ if (_id) this.props.history.push(`${ROUTES.EDIT_TRANSFORM}?id=${_id}`);
+ };
+
+ onClickDelete = async () => {
+ const { transformService } = this.props;
+ const { selectedItems } = this.state;
+ for (let item of selectedItems) {
+ const transformId = item._id;
+ try {
+ const response = await transformService.deleteTransform(transformId);
+
+ if (response.ok) {
+ this.closeDeleteModal();
+ this.context.notification.toasts.addSuccess(`"${transformId}" successfully deleted!`);
+ } else {
+ this.context.notifications.toasts.addDanger(`could not delete transform job "${transformId}" : ${response.error}`);
+ }
+ } catch (err) {
+ this.context.notification.toasts.addDanger(getErrorMessage(err, "Could not delete the transform job"));
+ }
+ }
+
+ await this.getTransforms();
+ };
+
+ onPageClick = (page: number) => {
+ this.setState({ from: page * this.state.size });
+ };
+
+ onTableChange = ({ page: tablePage, sort }: Criteria) => {
+ const { index: page, size } = tablePage;
+ const { field: sortField, direction: sortDirection } = sort;
+ this.setState({ from: page * size, size, sortField, sortDirection });
+ };
+
+ closePopover = () => {
+ this.setState({ isPopOverOpen: false });
+ };
+
+ resetFilters = () => {
+ this.setState({ search: DEFAULT_QUERY_PARAMS.search });
+ };
+
+ onEnable = async () => {
+ const { transformService } = this.props;
+ const { selectedItems } = this.state;
+
+ for (const item of selectedItems) {
+ const transformId = item._id;
+ try {
+ const response = await transformService.startTransform(transformId);
+
+ if (response.ok) {
+ this.context.notifications.toasts.addSuccess(`${transformId} is enabled`);
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not start transform job "${transformId}": ${response.error}`);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, `Could not start transform job ${transformId}`));
+ }
+ }
+
+ await this.getTransforms();
+ };
+
+ onDisable = async () => {
+ const { transformService } = this.props;
+ const { selectedItems } = this.state;
+
+ for (const item of selectedItems) {
+ const transformId = item._id;
+ try {
+ const response = await transformService.stopTransform(transformId);
+
+ if (response.ok) {
+ this.context.notifications.toasts.addSuccess(`${transformId} is disabled`);
+ } else {
+ this.context.notifications.toasts.addDanger(`Could not stop transform job "${transformId}": ${response.error}`);
+ }
+ } catch (err) {
+ this.context.notifications.toasts.addDanger(getErrorMessage(err, `Could not stop transform job ${transformId}`));
+ }
+ }
+
+ await this.getTransforms();
+ };
+
+ onSearchChange = (e: React.ChangeEvent) => {
+ this.setState({ from: 0, search: e.target.value });
+ };
+
+ onActionButtonClick = () => {
+ this.setState({ isPopOverOpen: !this.state.isPopOverOpen });
+ };
+
+ static getQueryObjectFromState({ from, size, search, sortField, sortDirection }: TransformState): TransformQueryParams {
+ return { from, size, search, sortField, sortDirection };
+ }
+}
diff --git a/public/pages/Transforms/containers/Transforms/index.ts b/public/pages/Transforms/containers/Transforms/index.ts
new file mode 100644
index 000000000..9ccbf849d
--- /dev/null
+++ b/public/pages/Transforms/containers/Transforms/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import EditTransform from "./EditTransform";
+import Transforms from "./Transforms";
+import TransformSettings from "./TransformSettings";
+
+export { Transforms, EditTransform, TransformSettings };
diff --git a/public/pages/Transforms/index.ts b/public/pages/Transforms/index.ts
new file mode 100644
index 000000000..7f224906b
--- /dev/null
+++ b/public/pages/Transforms/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import { EditTransform, Transforms } from "./containers/Transforms";
+
+export { Transforms, EditTransform };
diff --git a/public/pages/Transforms/models/interfaces.ts b/public/pages/Transforms/models/interfaces.ts
new file mode 100644
index 000000000..620afece3
--- /dev/null
+++ b/public/pages/Transforms/models/interfaces.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import { Direction } from "@elastic/eui";
+import {DocumentTransform} from "../../../../models/interfaces";
+
+export interface TransformQueryParams {
+ from: number;
+ size: number;
+ search: string;
+ sortField: keyof DocumentTransform;
+ sortDirection: Direction;
+}
diff --git a/public/pages/Transforms/utils/constants.tsx b/public/pages/Transforms/utils/constants.tsx
new file mode 100644
index 000000000..2adea0dbe
--- /dev/null
+++ b/public/pages/Transforms/utils/constants.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import { SortDirection } from "../../../utils/constants";
+
+// TODO: Consolidate with Rollup
+export const DEFAULT_QUERY_PARAMS = {
+ from: 0,
+ size: 20,
+ search: "",
+ sortField: "_id",
+ sortDirection: SortDirection.DESC,
+};
+
+export const EMPTY_TRANSFORM = JSON.stringify({
+ transform: {
+ description: "",
+ groups: [],
+ enabled: true,
+ aggregations: {},
+ data_selection_query: {},
+ roles: [],
+ schedule: {
+ interval: {
+ start_time: 234802,
+ period: 1,
+ unit: "MINUTES",
+ },
+ },
+ source_index: "",
+ target_index: "",
+ },
+});
+
+export const ScheduleIntervalTimeunitOptions = [
+ { value: "MINUTES", text: "Minute(s)" },
+ { value: "HOURS", text: "Hour(s)" },
+ { value: "DAYS", text: "Day(s)" },
+];
diff --git a/public/pages/Transforms/utils/helpers.ts b/public/pages/Transforms/utils/helpers.ts
new file mode 100644
index 000000000..687e289ab
--- /dev/null
+++ b/public/pages/Transforms/utils/helpers.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import queryString from "query-string";
+import { TransformQueryParams } from "../models/interfaces";
+import { DEFAULT_QUERY_PARAMS } from "./constants";
+import moment from "moment";
+
+export function getURLQueryParams(location: { search: string }): TransformQueryParams {
+ const { from, size, search, sortField, sortDirection } = queryString.parse(location.search);
+
+ return {
+ // @ts-ignores
+ from: isNaN(parseInt(from, 10)) ? DEFAULT_QUERY_PARAMS.from : parseInt(from, 10),
+ // @ts-ignores
+ size: isNaN(parseInt(size, 10)) ? DEFAULT_QUERY_PARAMS.size : parseInt(size, 10),
+ search: typeof search !== "string" ? DEFAULT_QUERY_PARAMS.search : search,
+ sortField: typeof sortField !== "string" ? DEFAULT_QUERY_PARAMS.sortField : sortField,
+ sortDirection: typeof sortDirection !== "string" ? DEFAULT_QUERY_PARAMS.sortDirection : sortDirection,
+ };
+}
+
+export const renderTime = (time: number): string => {
+ const momentTime = moment(time).local();
+ if (time && momentTime.isValid()) return momentTime.format("MM/DD/YY h:mmA");
+ return "-";
+};
diff --git a/public/pages/Transforms/utils/metadataHelper.tsx b/public/pages/Transforms/utils/metadataHelper.tsx
new file mode 100644
index 000000000..c05f4c6b5
--- /dev/null
+++ b/public/pages/Transforms/utils/metadataHelper.tsx
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import {TransformMetadata} from "../../../../models/interfaces";
+import React from "react";
+import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from "@elastic/eui";
+
+// TODO: merge with rollup helper to have a common helper
+export const renderStatus = (metadata: TransformMetadata | undefined): JSX.Element => {
+ if (metadata == null || metadata.transform_metadata == null) return -;
+ let icon;
+ let iconColor;
+ let textColor: "default" | "subdued" | "secondary" | "ghost" | "accent" | "warning" | "danger" | undefined;
+ let text;
+ switch (metadata.transform_metadata.status) {
+ case "failed":
+ icon = "alert";
+ iconColor = "danger";
+ textColor = "danger";
+ text = "Failed: " + metadata.transform_metadata.failure_reason;
+ break;
+ case "finished":
+ icon = "check";
+ iconColor = "success";
+ textColor = "secondary";
+ text = "Complete";
+ break;
+ case "init":
+ return (
+
+
+
+
+
+
+ Initializing
+
+
+
+ );
+ case "started":
+ icon = "play";
+ iconColor = "success";
+ textColor = "secondary";
+ text = "Started";
+ break;
+ case "stopped":
+ icon = "stop";
+ iconColor = "subdued";
+ textColor = "subdued";
+ text = "Stopped";
+ break;
+ default:
+ return -;
+ }
+
+ return (
+
+
+
+
+
+
+ {text}
+
+
+
+ );
+};
+
+export const renderEnabled = (isEnabled: boolean): string => {
+ return isEnabled ? "Enabled" : "Disabled";
+};
diff --git a/public/services/TransformService.ts b/public/services/TransformService.ts
new file mode 100644
index 000000000..718fea4ed
--- /dev/null
+++ b/public/services/TransformService.ts
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+import { HttpSetup } from "kibana/public";
+import { ServerResponse } from "../../server/models/types";
+import { GetTransformsResponse, PutTransformResponse } from "../../server/models/interfaces";
+import { NODE_API } from "../../utils/constants";
+import { DocumentTransform, Transform } from "../../models/interfaces";
+
+export default class TransformService {
+ httpClient: HttpSetup;
+
+ constructor(httpClient: HttpSetup) {
+ this.httpClient = httpClient;
+ }
+
+ getTransforms = async (queryObject: object): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}`;
+ // @ts-ignore
+ return (await this.httpClient.get(url, { query: queryObject })) as ServerResponse;
+ };
+
+ putTransform = async (
+ transform: Transform,
+ transformId: string,
+ seqNo?: number,
+ primaryTerm?: number
+ ): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}/${transformId}`;
+ return (await this.httpClient.put(url, { query: { seqNo, primaryTerm }, body: JSON.stringify(transform) })) as ServerResponse<
+ PutTransformResponse
+ >;
+ };
+
+ getTransform = async (transformId: string): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}/${transformId}`;
+ return (await this.httpClient.get(url)) as ServerResponse;
+ };
+
+ deleteTransform = async (transformId: string): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}/${transformId}`;
+ return (await this.httpClient.delete(url)) as ServerResponse;
+ };
+
+ startTransform = async (transformId: string): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}/${transformId}/_start`;
+ return (await this.httpClient.post(url)) as ServerResponse;
+ };
+
+ stopTransform = async (transformId: string): Promise> => {
+ const url = `..${NODE_API.TRANSFORMS}/${transformId}/_stop`;
+ return (await this.httpClient.post(url)) as ServerResponse;
+ };
+
+ previewTransform = async (transform: Map): Promise>> => {
+ const url = `..${NODE_API.TRANSFORMS}/_preview`;
+ return (await this.httpClient.post(url, { body: JSON.stringify(transform) })) as ServerResponse