Skip to content

Commit

Permalink
Add notification
Browse files Browse the repository at this point in the history
Signed-off-by: bowenlan-amzn <[email protected]>
  • Loading branch information
bowenlan-amzn committed Jun 27, 2022
1 parent f08cbe2 commit fa8f94e
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,15 @@ const CronSchedule = ({
<EuiFlexItem style={{ maxWidth: 400 }}>
{frequencyType === "custom" ? (
<>
<CustomLabel title="Cron expression" helpText={cronExpressionHelpText} />
<EuiFieldText
value={cronExpression}
onChange={(e) => {
onCronExpressionChange(e.target.value);
}}
/>
<CustomLabel title="Cron expression" />
<EuiFormRow helpText={cronExpressionHelpText}>
<EuiFieldText
value={cronExpression}
onChange={(e) => {
onCronExpressionChange(e.target.value);
}}
/>
</EuiFormRow>
</>
) : (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { ChangeEvent } from "react";
import { EuiFormRow, EuiSelect, EuiButton, EuiFlexGroup, EuiFlexItem } from "@elastic/eui";
import "brace/theme/github";
import "brace/mode/json";
import { FeatureChannelList } from "../../../../../server/models/interfaces";
import CustomLabel from "../../../../components/CustomLabel";

interface NotificationProps {
channelId: string;
channels: FeatureChannelList[];
loadingChannels: boolean;
onChangeChannelId: (value: ChangeEvent<HTMLSelectElement>) => void;
getChannels: () => void;
}

const Notification = ({ channelId, channels, loadingChannels, onChangeChannelId, getChannels }: NotificationProps) => {
return (
<>
<CustomLabel title="Select notification channels" />
<EuiFlexGroup gutterSize="s" style={{ maxWidth: 600 }}>
<EuiFlexItem>
<EuiFormRow>
<EuiSelect
id="channel-id"
placeholder="Select channel ID"
hasNoInitialSelection
isLoading={loadingChannels}
options={channels.map((channel) => ({ value: channel.config_id, text: channel.name }))}
value={channelId}
onChange={onChangeChannelId}
data-test-subj="create-policy-notification-channel-id"
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
iconType="refresh"
onClick={getChannels}
disabled={loadingChannels}
className="refresh-button"
data-test-subj="channel-notification-refresh"
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton iconType="popout" href="notifications-dashboards#/channels" target="_blank">
Manage channels
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
};

export default Notification;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import Notification from "./Notification";

export default Notification;
2 changes: 1 addition & 1 deletion public/pages/CreateSnapshotPolicy/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const maxAgeUnitOptions = [
{ value: "h", text: "Hours" },
];

export const DEFAULT_INDEX_OPTIONS = [{ label: "*" }, { label: "-.opendistro_security" }];
export const DEFAULT_INDEX_OPTIONS = [{ label: "*" }];

export const ERROR_PROMPT = {
NAME: "Name must be provided.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@ import { BREADCRUMBS, ROUTES, SNAPSHOT_MANAGEMENT_DOCUMENTATION_URL } from "../.
import { ContentPanel } from "../../../../components/ContentPanel";
import { IndexService, NotificationService, SnapshotManagementService } from "../../../../services";
import { getErrorMessage, wildcardOption } from "../../../../utils/helpers";
import SnapshotIndicesRepoInput from "../../components/SnapshotIndicesRepoInput/SnapshotIndicesRepoInput";
import CronSchedule from "../../components/CronSchedule/CronSchedule";
import SnapshotAdvancedSettings from "../../components/SnapshotAdvancedSettings/SnapshotAdvancedSettings";
import CustomLabel from "../../../../components/CustomLabel";
import ChannelNotification from "../../../VisualCreatePolicy/components/ChannelNotification";
import { DEFAULT_INDEX_OPTIONS, ERROR_PROMPT, getDefaultSMPolicy, maxAgeUnitOptions as MAX_AGE_UNIT_OPTIONS } from "../../constants";
import {
getIncludeGlobalState,
Expand All @@ -54,6 +50,10 @@ import {
} from "../helper";
import { parseCronExpression } from "../../components/CronSchedule/helper";
import { TIMEZONES } from "../../components/CronSchedule/constants";
import SnapshotIndicesRepoInput from "../../components/SnapshotIndicesRepoInput";
import CronSchedule from "../../components/CronSchedule";
import SnapshotAdvancedSettings from "../../components/SnapshotAdvancedSettings";
import Notification from "../../components/Notification";

interface CreateSMPolicyProps extends RouteComponentProps {
snapshotManagementService: SnapshotManagementService;
Expand Down Expand Up @@ -86,7 +86,7 @@ interface CreateSMPolicyState {
deletionScheduleFrequencyType: string;

deleteConditionEnabled: boolean;
deletionScheduleEnabled: boolean;
deletionScheduleEnabled: boolean; // whether to use the same schedule as creation

advancedSettingsOpen: boolean;

Expand Down Expand Up @@ -164,6 +164,7 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
}
await this.getIndexOptions("");
await this.getRepos();
await this.getChannels();
}

getPolicy = async (policyId: string): Promise<void> => {
Expand All @@ -172,8 +173,7 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
const response = await snapshotManagementService.getPolicy(policyId);

if (response.ok) {
this.populatePolicyToState(response.response.policy);

// Populate policy into state
const policy = response.response.policy;
const indices = _.get(policy, "snapshot_config.indices", "");
const selectedIndexOptions = indices
Expand All @@ -182,10 +182,26 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
.map((label: string) => ({ label }));
const selectedRepoValue = _.get(policy, "snapshot_config.repository", "");

// TODO SM parse frequency type
const { frequencyType: creationScheduleFrequencyType } = parseCronExpression(_.get(policy, "creation.schedule.cron.expression"));
const { frequencyType: deletionScheduleFrequencyType } = parseCronExpression(_.get(policy, "deletion.schedule.cron.expression"));

let deleteConditionEnabled = false;
let deletionScheduleEnabled = false;
if (!!_.get(policy, "deletion")) {
deleteConditionEnabled = true;
const creationScheduleExpression = _.get(policy, "creation.schedule.cron.expression");
const deletionScheduleExpression = _.get(policy, "deletion.schedule.cron.expression");
if (creationScheduleExpression !== deletionScheduleExpression) deletionScheduleEnabled = true;
}

const maxAge = policy.deletion?.condition?.max_age;
let maxAgeNum = 1;
let maxAgeUnit = "d";
if (maxAge) {
maxAgeNum = parseInt(maxAge.substring(0, maxAge.length - 1));
maxAgeUnit = maxAge[maxAge.length - 1];
}

this.setState({
policy,
policyId: response.response.id,
Expand All @@ -195,6 +211,10 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
selectedRepoValue,
creationScheduleFrequencyType,
deletionScheduleFrequencyType,
deleteConditionEnabled,
deletionScheduleEnabled,
maxAgeNum,
maxAgeUnit,
});
} else {
const errorMessage = response.ok ? "Policy was empty" : response.error;
Expand Down Expand Up @@ -337,7 +357,6 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
console.error(err);
this.setState({ isSubmitting: false });
}
this.setState({ isSubmitting: false });
};

buildPolicyFromState = (policy: SMPolicy): SMPolicy => {
Expand Down Expand Up @@ -396,7 +415,6 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
};

getChannels = async (): Promise<void> => {
console.log(`sm dev get channels`);
this.setState({ loadingChannels: true });
try {
const { notificationService } = this.props;
Expand All @@ -417,7 +435,7 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
};

render() {
// console.log(`sm dev render state snapshotconfig ${JSON.stringify(this.state.policy.snapshot_config)}`);
// console.log(`sm dev render state policy ${JSON.stringify(this.state.policy)}`);

const { isEdit } = this.props;
const {
Expand Down Expand Up @@ -625,6 +643,7 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
this.setState({ deletionScheduleEnabled: !deletionScheduleEnabled });
}}
/>
<EuiSpacer size="s" />

{deletionScheduleEnabled ? (
<CronSchedule
Expand All @@ -639,7 +658,7 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
}}
/>
) : null}
</EuiAccordion>{" "}
</EuiAccordion>
</>
) : null}
</ContentPanel>
Expand All @@ -652,8 +671,8 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,

<EuiSpacer size="s" />
<EuiCheckbox
id="creation"
label="When a snapshot gets created."
id="notification-creation"
label="When a snapshot has been created."
checked={notifyOnCreation}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
this.setState({ policy: this.setPolicyHelper("notification.conditions.creation", e.target.checked) });
Expand All @@ -663,8 +682,8 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
<EuiSpacer size="s" />

<EuiCheckbox
id="deletion"
label="when snapshots get deleted."
id="notification-deletion"
label="when a snapshots has been deleted."
checked={notifyOnDeletion}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
this.setState({ policy: this.setPolicyHelper("notification.conditions.deletion", e.target.checked) });
Expand All @@ -674,17 +693,17 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
<EuiSpacer size="s" />

<EuiCheckbox
id="partial"
label="When a snapshot operation failed."
id="notification-failure"
label="When a snapshot has failed."
checked={notifyOnFailure}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
this.setState({ policy: this.setPolicyHelper("notification.conditions.failure", e.target.checked) });
}}
/>
</div>
{showNotificationChannel ? (
<ChannelNotification
channelId=""
<Notification
channelId={_.get(policy, "notification.channel.id") || ""}
channels={channels}
loadingChannels={loadingChannels}
onChangeChannelId={this.onChangeChannelId}
Expand Down Expand Up @@ -740,7 +759,7 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,

<CustomLabel title="Timestamp format" />
<EuiFieldText
placeholder="e.g. yyyy-MM-dd-HH:mm"
placeholder="e.g., yyyy-MM-dd-HH:mm"
value={_.get(policy, "snapshot_config.date_format")}
onChange={(e) => {
this.setState({ policy: this.setPolicyHelper("snapshot_config.date_format", e.target.value) });
Expand Down Expand Up @@ -783,16 +802,6 @@ export default class CreateSnapshotPolicy extends Component<CreateSMPolicyProps,
);
}

populatePolicyToState = (policy: SMPolicy) => {
const maxAge = policy.deletion?.condition?.max_age;
if (maxAge) {
this.setState({
maxAgeNum: parseInt(maxAge.substring(0, maxAge.length - 1)),
maxAgeUnit: maxAge[maxAge.length - 1],
});
}
};

onChangeMaxCount = (e: ChangeEvent<HTMLInputElement>) => {
// Received NaN for the `value` attribute. If this is expected, cast the value to a string.
const maxCount = isNaN(parseInt(e.target.value)) ? undefined : parseInt(e.target.value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default class SnapshotPolicies extends Component<SnapshotPoliciesProps, S
name: "Policy name",
sortable: true,
dataType: "string",
width: "180px",
width: "150px",
render: (name: string, item: SMPolicy) => {
const showSymbol = _.truncate(name, { length: 20 });
return (
Expand Down Expand Up @@ -126,6 +126,7 @@ export default class SnapshotPolicies extends Component<SnapshotPoliciesProps, S
name: "Indices",
sortable: false,
dataType: "string",
width: "150px",
render: (value: string, item: SMPolicy) => {
return truncateSpan(value);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,6 @@ export default class SnapshotPolicyDetails extends Component<SnapshotPolicyDetai
);
}

// console.log(`sm dev policy ${JSON.stringify(policy)}`);

const policySettingItems = [
{ term: "Policy name", value: truncateSpan(policyId, 30) },
{ term: "Status", value: this.renderEnabledField(policy.enabled) },
Expand Down Expand Up @@ -266,7 +264,6 @@ export default class SnapshotPolicyDetails extends Component<SnapshotPolicyDetai
.filter((key) => notiConditions[key])
.join(", ");
}
console.log(`sm dev notification ${notiActivities}`);

const notificationItems = [
{ term: "Notify on snapshot activities", value: notiActivities },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ export default class CreateSnapshotFlyout extends Component<CreateSnapshotProps,
this.setState({ repoError });
return;
}
// console.log(`sm dev snapshot body ${JSON.stringify(snapshot)}`);
createSnapshot(snapshotId, selectedRepoValue, snapshot);
};

Expand Down
10 changes: 6 additions & 4 deletions public/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ export const ACTIONS_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/
export const STATES_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ism/policies/#states";
export const ERROR_NOTIFICATION_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ism/policies/#error-notifications";
export const TRANSITION_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ism/policies/#transitions";

export const SNAPSHOT_MANAGEMENT_DOCUMENTATION_URL = "https://opensearch.org//docs/latest/opensearch/snapshots/snapshot-management/";
export const CRON_EXPRESSION_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/monitoring-plugins/alerting/cron/";
export const SNAPSHOT_MANAGEMENT_DOCUMENTATION_URL = "https://opensearch.org/docs/im-plugin/ism/index";
export const REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshot-restore/#register-repository";
export const FS_REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshot-restore/#shared-file-system";
export const S3_REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshot-restore/#amazon-s3";
export const REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshots/snapshot-restore/#register-repository";
export const FS_REPOSITORY_DOCUMENTATION_URL =
"https://opensearch.org/docs/latest/opensearch/snapshots/snapshot-restore/#shared-file-system";
export const S3_REPOSITORY_DOCUMENTATION_URL = "https://opensearch.org/docs/latest/opensearch/snapshots/snapshot-restore/#amazon-s3";

export const ROUTES = Object.freeze({
CHANGE_POLICY: "/change-policy",
Expand Down
7 changes: 1 addition & 6 deletions server/services/SnapshotManagementService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,6 @@ export default class SnapshotManagementService {
repository: repositories[i],
policy: s.metadata?.sm_policy,
}));
// TODO SM try catch the missing snapshot exception
// const catSnapshotsRes: CatSnapshotWithRepoAndPolicy[] = await callWithRequest("snapshot.get", params);
// const snapshotsWithRepo = catSnapshotsRes.map((item) => ({ ...item, repository: repositories[i] }));
// console.log(`sm dev cat snapshot response: ${JSON.stringify(snapshotWithPolicy)}`);
snapshots = [...snapshots, ...snapshotWithPolicy];
}

Expand All @@ -93,7 +89,7 @@ export default class SnapshotManagementService {
},
});
} catch (err) {
// TODO SM handle missing snapshot exception, return empty
// If getting a non-existing snapshot, need to handle the missing snapshot exception, and return empty
return this.errorResponse(response, err, "getAllSnapshotsWithPolicy");
}
};
Expand Down Expand Up @@ -176,7 +172,6 @@ export default class SnapshotManagementService {
snapshot: id,
body: JSON.stringify(request.body),
};
// TODO SM body indices, ignore_unavailable, include_global_state, partial
const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request);
const resp: CreateSnapshotResponse = await callWithRequest("snapshot.create", params);

Expand Down

0 comments on commit fa8f94e

Please sign in to comment.