mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 14:15:55 +08:00
commit
865409d725
@ -133,7 +133,7 @@ services:
|
||||
# - ./data/clickhouse-3/:/var/lib/clickhouse/
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.23.1
|
||||
image: signoz/alertmanager:0.23.2
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
command:
|
||||
@ -146,7 +146,7 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.25.5
|
||||
image: signoz/query-service:0.26.0
|
||||
command: [ "-config=/root/config/prometheus.yml" ]
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
@ -182,7 +182,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.25.5
|
||||
image: signoz/frontend:0.26.0
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
@ -34,7 +34,7 @@ services:
|
||||
|
||||
alertmanager:
|
||||
container_name: signoz-alertmanager
|
||||
image: signoz/alertmanager:0.23.1
|
||||
image: signoz/alertmanager:0.23.2
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
depends_on:
|
||||
|
@ -148,7 +148,7 @@ services:
|
||||
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.1}
|
||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.2}
|
||||
container_name: signoz-alertmanager
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
@ -163,7 +163,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.25.5}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.26.0}
|
||||
container_name: signoz-query-service
|
||||
command: [ "-config=/root/config/prometheus.yml" ]
|
||||
# ports:
|
||||
@ -198,7 +198,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.25.5}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.26.0}
|
||||
container_name: signoz-frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
|
@ -39,6 +39,9 @@ COPY --from=builder /go/src/github.com/signoz/signoz/ee/query-service/bin/query-
|
||||
# copy prometheus YAML config
|
||||
COPY pkg/query-service/config/prometheus.yml /root/config/prometheus.yml
|
||||
|
||||
# Make query-service executable for non-root users
|
||||
RUN chmod 755 /root /root/query-service
|
||||
|
||||
# run the binary
|
||||
ENTRYPOINT ["./query-service"]
|
||||
|
||||
|
@ -60,6 +60,34 @@ var BasicPlan = basemodel.FeatureSet{
|
||||
UsageLimit: 5,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelSlack,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelWebhook,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelPagerduty,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.UseSpanMetrics,
|
||||
Active: false,
|
||||
@ -112,6 +140,34 @@ var ProPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelSlack,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelWebhook,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelPagerduty,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.UseSpanMetrics,
|
||||
Active: false,
|
||||
@ -164,6 +220,34 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelSlack,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelWebhook,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelPagerduty,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.UseSpanMetrics,
|
||||
Active: false,
|
||||
|
@ -24,7 +24,7 @@ COPY . .
|
||||
RUN yarn build
|
||||
|
||||
|
||||
FROM nginx:1.18-alpine
|
||||
FROM nginx:1.24.0-alpine
|
||||
|
||||
COPY conf/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
|
@ -1,112 +1,113 @@
|
||||
{
|
||||
"target_missing": "Please enter a threshold to proceed",
|
||||
"rule_test_fired": "Test notification sent successfully",
|
||||
"no_alerts_found": "No alerts found during the evaluation. This happens when rule condition is unsatisfied. You may adjust the rule threshold and retry.",
|
||||
"button_testrule": "Test Notification",
|
||||
"label_channel_select": "Notification Channels",
|
||||
"placeholder_channel_select": "select one or more channels",
|
||||
"channel_select_tooltip": "Leave empty to send this alert on all the configured channels",
|
||||
"preview_chart_unexpected_error": "An unexpeced error occurred updating the chart, please check your query.",
|
||||
"preview_chart_threshold_label": "Threshold",
|
||||
"placeholder_label_key_pair": "Click here to enter a label (key value pairs)",
|
||||
"button_yes": "Yes",
|
||||
"button_no": "No",
|
||||
"remove_label_confirm": "This action will remove all the labels. Do you want to proceed?",
|
||||
"remove_label_success": "Labels cleared",
|
||||
"alert_form_step1": "Step 1 - Define the metric",
|
||||
"alert_form_step2": "Step 2 - Define Alert Conditions",
|
||||
"alert_form_step3": "Step 3 - Alert Configuration",
|
||||
"metric_query_max_limit": "Can not create query. You can create maximum of 5 queries",
|
||||
"confirm_save_title": "Save Changes",
|
||||
"confirm_save_content_part1": "Your alert built with",
|
||||
"confirm_save_content_part2": "query will be saved. Press OK to confirm.",
|
||||
"unexpected_error": "Sorry, an unexpected error occurred. Please contact your admin",
|
||||
"rule_created": "Rule created successfully",
|
||||
"rule_edited": "Rule edited successfully",
|
||||
"expression_missing": "expression is missing in {{where}}",
|
||||
"metricname_missing": "metric name is missing in {{where}}",
|
||||
"condition_required": "at least one metric condition is required",
|
||||
"alertname_required": "alert name is required",
|
||||
"promql_required": "promql expression is required when query format is set to PromQL",
|
||||
"chquery_required": "query is required when query format is set to ClickHouse",
|
||||
"button_savechanges": "Save Rule",
|
||||
"button_createrule": "Create Rule",
|
||||
"button_returntorules": "Return to rules",
|
||||
"button_cancelchanges": "Cancel",
|
||||
"button_discard": "Discard",
|
||||
"text_condition1": "Send a notification when the metric is",
|
||||
"text_condition2": "the threshold",
|
||||
"text_condition3": "during the last",
|
||||
"option_5min": "5 mins",
|
||||
"option_10min": "10 mins",
|
||||
"option_15min": "15 mins",
|
||||
"option_60min": "60 mins",
|
||||
"option_4hours": "4 hours",
|
||||
"option_24hours": "24 hours",
|
||||
"field_threshold": "Alert Threshold",
|
||||
"option_allthetimes": "all the times",
|
||||
"option_atleastonce": "at least once",
|
||||
"option_onaverage": "on average",
|
||||
"option_intotal": "in total",
|
||||
"option_above": "above",
|
||||
"option_below": "below",
|
||||
"option_equal": "is equal to",
|
||||
"option_notequal": "not equal to",
|
||||
"button_query": "Query",
|
||||
"button_formula": "Formula",
|
||||
"tab_qb": "Query Builder",
|
||||
"tab_promql": "PromQL",
|
||||
"tab_chquery": "ClickHouse Query",
|
||||
"title_confirm": "Confirm",
|
||||
"button_ok": "Yes",
|
||||
"button_cancel": "No",
|
||||
"field_promql_expr": "PromQL Expression",
|
||||
"field_alert_name": "Alert Name",
|
||||
"field_alert_desc": "Alert Description",
|
||||
"field_labels": "Labels",
|
||||
"field_severity": "Severity",
|
||||
"option_critical": "Critical",
|
||||
"option_error": "Error",
|
||||
"option_warning": "Warning",
|
||||
"option_info": "Info",
|
||||
"user_guide_headline": "Steps to create an Alert",
|
||||
"user_guide_qb_step1": "Step 1 - Define the metric",
|
||||
"user_guide_qb_step1a": "Choose a metric which you want to create an alert on",
|
||||
"user_guide_qb_step1b": "Filter it based on WHERE field or GROUPBY if needed",
|
||||
"user_guide_qb_step1c": "Apply an aggregatiion function like COUNT, SUM, etc. or choose NOOP to plot the raw metric",
|
||||
"user_guide_qb_step1d": "Create a formula based on Queries if needed",
|
||||
"user_guide_qb_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_qb_step2a": "Select the evaluation interval, threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_qb_step2b": "Enter the Alert threshold",
|
||||
"user_guide_qb_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_qb_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_qb_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_guide_pql_step1": "Step 1 - Define the metric",
|
||||
"user_guide_pql_step1a": "Write a PromQL query for the metric",
|
||||
"user_guide_pql_step1b": "Format the legends based on labels you want to highlight",
|
||||
"user_guide_pql_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_pql_step2a": "Select the threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_pql_step2b": "Enter the Alert threshold",
|
||||
"user_guide_pql_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_pql_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_pql_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_guide_ch_step1": "Step 1 - Define the metric",
|
||||
"user_guide_ch_step1a": "Write a Clickhouse query for alert evaluation. Follow <0>this tutorial</0> to learn about query format and supported vars.",
|
||||
"user_guide_ch_step1b": "Format the legends based on labels you want to highlight in the preview chart",
|
||||
"user_guide_ch_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_ch_step2a": "Select the threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_ch_step2b": "Enter the Alert threshold",
|
||||
"user_guide_ch_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_ch_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_ch_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_tooltip_more_help": "More details on how to create alerts",
|
||||
"choose_alert_type": "Choose a type for the alert:",
|
||||
"metric_based_alert": "Metric based Alert",
|
||||
"metric_based_alert_desc": "Send a notification when a condition occurs in the metric data",
|
||||
"log_based_alert": "Log-based Alert",
|
||||
"log_based_alert_desc": "Send a notification when a condition occurs in the logs data.",
|
||||
"traces_based_alert": "Trace-based Alert",
|
||||
"traces_based_alert_desc": "Send a notification when a condition occurs in the traces data.",
|
||||
"exceptions_based_alert": "Exceptions-based Alert",
|
||||
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data."
|
||||
"target_missing": "Please enter a threshold to proceed",
|
||||
"rule_test_fired": "Test notification sent successfully",
|
||||
"no_alerts_found": "No alerts found during the evaluation. This happens when rule condition is unsatisfied. You may adjust the rule threshold and retry.",
|
||||
"button_testrule": "Test Notification",
|
||||
"label_channel_select": "Notification Channels",
|
||||
"placeholder_channel_select": "select one or more channels",
|
||||
"channel_select_tooltip": "Leave empty to send this alert on all the configured channels",
|
||||
"preview_chart_unexpected_error": "An unexpeced error occurred updating the chart, please check your query.",
|
||||
"preview_chart_threshold_label": "Threshold",
|
||||
"placeholder_label_key_pair": "Click here to enter a label (key value pairs)",
|
||||
"button_yes": "Yes",
|
||||
"button_no": "No",
|
||||
"remove_label_confirm": "This action will remove all the labels. Do you want to proceed?",
|
||||
"remove_label_success": "Labels cleared",
|
||||
"alert_form_step1": "Step 1 - Define the metric",
|
||||
"alert_form_step2": "Step 2 - Define Alert Conditions",
|
||||
"alert_form_step3": "Step 3 - Alert Configuration",
|
||||
"metric_query_max_limit": "Can not create query. You can create maximum of 5 queries",
|
||||
"confirm_save_title": "Save Changes",
|
||||
"confirm_save_content_part1": "Your alert built with",
|
||||
"confirm_save_content_part2": "query will be saved. Press OK to confirm.",
|
||||
"unexpected_error": "Sorry, an unexpected error occurred. Please contact your admin",
|
||||
"rule_created": "Rule created successfully",
|
||||
"rule_edited": "Rule edited successfully",
|
||||
"expression_missing": "expression is missing in {{where}}",
|
||||
"metricname_missing": "metric name is missing in {{where}}",
|
||||
"condition_required": "at least one metric condition is required",
|
||||
"alertname_required": "alert name is required",
|
||||
"promql_required": "promql expression is required when query format is set to PromQL",
|
||||
"chquery_required": "query is required when query format is set to ClickHouse",
|
||||
"button_savechanges": "Save Rule",
|
||||
"button_createrule": "Create Rule",
|
||||
"button_returntorules": "Return to rules",
|
||||
"button_cancelchanges": "Cancel",
|
||||
"button_discard": "Discard",
|
||||
"text_condition1": "Send a notification when the metric is",
|
||||
"text_condition2": "the threshold",
|
||||
"text_condition3": "during the last",
|
||||
"option_5min": "5 mins",
|
||||
"option_10min": "10 mins",
|
||||
"option_15min": "15 mins",
|
||||
"option_60min": "60 mins",
|
||||
"option_4hours": "4 hours",
|
||||
"option_24hours": "24 hours",
|
||||
"field_threshold": "Alert Threshold",
|
||||
"option_allthetimes": "all the times",
|
||||
"option_atleastonce": "at least once",
|
||||
"option_onaverage": "on average",
|
||||
"option_intotal": "in total",
|
||||
"option_above": "above",
|
||||
"option_below": "below",
|
||||
"option_equal": "is equal to",
|
||||
"option_notequal": "not equal to",
|
||||
"button_query": "Query",
|
||||
"button_formula": "Formula",
|
||||
"tab_qb": "Query Builder",
|
||||
"tab_promql": "PromQL",
|
||||
"tab_chquery": "ClickHouse Query",
|
||||
"title_confirm": "Confirm",
|
||||
"button_ok": "Yes",
|
||||
"button_cancel": "No",
|
||||
"field_promql_expr": "PromQL Expression",
|
||||
"field_alert_name": "Alert Name",
|
||||
"field_alert_desc": "Alert Description",
|
||||
"field_labels": "Labels",
|
||||
"field_severity": "Severity",
|
||||
"option_critical": "Critical",
|
||||
"option_error": "Error",
|
||||
"option_warning": "Warning",
|
||||
"option_info": "Info",
|
||||
"user_guide_headline": "Steps to create an Alert",
|
||||
"user_guide_qb_step1": "Step 1 - Define the metric",
|
||||
"user_guide_qb_step1a": "Choose a metric which you want to create an alert on",
|
||||
"user_guide_qb_step1b": "Filter it based on WHERE field or GROUPBY if needed",
|
||||
"user_guide_qb_step1c": "Apply an aggregatiion function like COUNT, SUM, etc. or choose NOOP to plot the raw metric",
|
||||
"user_guide_qb_step1d": "Create a formula based on Queries if needed",
|
||||
"user_guide_qb_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_qb_step2a": "Select the evaluation interval, threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_qb_step2b": "Enter the Alert threshold",
|
||||
"user_guide_qb_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_qb_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_qb_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_guide_pql_step1": "Step 1 - Define the metric",
|
||||
"user_guide_pql_step1a": "Write a PromQL query for the metric",
|
||||
"user_guide_pql_step1b": "Format the legends based on labels you want to highlight",
|
||||
"user_guide_pql_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_pql_step2a": "Select the threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_pql_step2b": "Enter the Alert threshold",
|
||||
"user_guide_pql_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_pql_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_pql_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_guide_ch_step1": "Step 1 - Define the metric",
|
||||
"user_guide_ch_step1a": "Write a Clickhouse query for alert evaluation. Follow <0>this tutorial</0> to learn about query format and supported vars.",
|
||||
"user_guide_ch_step1b": "Format the legends based on labels you want to highlight in the preview chart",
|
||||
"user_guide_ch_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_ch_step2a": "Select the threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_ch_step2b": "Enter the Alert threshold",
|
||||
"user_guide_ch_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_ch_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_ch_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_tooltip_more_help": "More details on how to create alerts",
|
||||
"choose_alert_type": "Choose a type for the alert:",
|
||||
"metric_based_alert": "Metric based Alert",
|
||||
"metric_based_alert_desc": "Send a notification when a condition occurs in the metric data",
|
||||
"log_based_alert": "Log-based Alert",
|
||||
"log_based_alert_desc": "Send a notification when a condition occurs in the logs data.",
|
||||
"traces_based_alert": "Trace-based Alert",
|
||||
"traces_based_alert_desc": "Send a notification when a condition occurs in the traces data.",
|
||||
"exceptions_based_alert": "Exceptions-based Alert",
|
||||
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
|
||||
"field_unit": "Threshold unit"
|
||||
}
|
@ -1,112 +1,113 @@
|
||||
{
|
||||
"target_missing": "Please enter a threshold to proceed",
|
||||
"rule_test_fired": "Test notification sent successfully",
|
||||
"no_alerts_found": "No alerts found during the evaluation. This happens when rule condition is unsatisfied. You may adjust the rule threshold and retry.",
|
||||
"button_testrule": "Test Notification",
|
||||
"label_channel_select": "Notification Channels",
|
||||
"placeholder_channel_select": "select one or more channels",
|
||||
"channel_select_tooltip": "Leave empty to send this alert on all the configured channels",
|
||||
"preview_chart_unexpected_error": "An unexpeced error occurred updating the chart, please check your query.",
|
||||
"preview_chart_threshold_label": "Threshold",
|
||||
"placeholder_label_key_pair": "Click here to enter a label (key value pairs)",
|
||||
"button_yes": "Yes",
|
||||
"button_no": "No",
|
||||
"remove_label_confirm": "This action will remove all the labels. Do you want to proceed?",
|
||||
"remove_label_success": "Labels cleared",
|
||||
"alert_form_step1": "Step 1 - Define the metric",
|
||||
"alert_form_step2": "Step 2 - Define Alert Conditions",
|
||||
"alert_form_step3": "Step 3 - Alert Configuration",
|
||||
"metric_query_max_limit": "Can not create query. You can create maximum of 5 queries",
|
||||
"confirm_save_title": "Save Changes",
|
||||
"confirm_save_content_part1": "Your alert built with",
|
||||
"confirm_save_content_part2": "query will be saved. Press OK to confirm.",
|
||||
"unexpected_error": "Sorry, an unexpected error occurred. Please contact your admin",
|
||||
"rule_created": "Rule created successfully",
|
||||
"rule_edited": "Rule edited successfully",
|
||||
"expression_missing": "expression is missing in {{where}}",
|
||||
"metricname_missing": "metric name is missing in {{where}}",
|
||||
"condition_required": "at least one metric condition is required",
|
||||
"alertname_required": "alert name is required",
|
||||
"promql_required": "promql expression is required when query format is set to PromQL",
|
||||
"chquery_required": "query is required when query format is set to ClickHouse",
|
||||
"button_savechanges": "Save Rule",
|
||||
"button_createrule": "Create Rule",
|
||||
"button_returntorules": "Return to rules",
|
||||
"button_cancelchanges": "Cancel",
|
||||
"button_discard": "Discard",
|
||||
"text_condition1": "Send a notification when the metric is",
|
||||
"text_condition2": "the threshold",
|
||||
"text_condition3": "during the last",
|
||||
"option_5min": "5 mins",
|
||||
"option_10min": "10 mins",
|
||||
"option_15min": "15 mins",
|
||||
"option_60min": "60 mins",
|
||||
"option_4hours": "4 hours",
|
||||
"option_24hours": "24 hours",
|
||||
"field_threshold": "Alert Threshold",
|
||||
"option_allthetimes": "all the times",
|
||||
"option_atleastonce": "at least once",
|
||||
"option_onaverage": "on average",
|
||||
"option_intotal": "in total",
|
||||
"option_above": "above",
|
||||
"option_below": "below",
|
||||
"option_equal": "is equal to",
|
||||
"option_notequal": "not equal to",
|
||||
"button_query": "Query",
|
||||
"button_formula": "Formula",
|
||||
"tab_qb": "Query Builder",
|
||||
"tab_promql": "PromQL",
|
||||
"tab_chquery": "ClickHouse Query",
|
||||
"title_confirm": "Confirm",
|
||||
"button_ok": "Yes",
|
||||
"button_cancel": "No",
|
||||
"field_promql_expr": "PromQL Expression",
|
||||
"field_alert_name": "Alert Name",
|
||||
"field_alert_desc": "Alert Description",
|
||||
"field_labels": "Labels",
|
||||
"field_severity": "Severity",
|
||||
"option_critical": "Critical",
|
||||
"option_error": "Error",
|
||||
"option_warning": "Warning",
|
||||
"option_info": "Info",
|
||||
"user_guide_headline": "Steps to create an Alert",
|
||||
"user_guide_qb_step1": "Step 1 - Define the metric",
|
||||
"user_guide_qb_step1a": "Choose a metric which you want to create an alert on",
|
||||
"user_guide_qb_step1b": "Filter it based on WHERE field or GROUPBY if needed",
|
||||
"user_guide_qb_step1c": "Apply an aggregatiion function like COUNT, SUM, etc. or choose NOOP to plot the raw metric",
|
||||
"user_guide_qb_step1d": "Create a formula based on Queries if needed",
|
||||
"user_guide_qb_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_qb_step2a": "Select the evaluation interval, threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_qb_step2b": "Enter the Alert threshold",
|
||||
"user_guide_qb_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_qb_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_qb_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_guide_pql_step1": "Step 1 - Define the metric",
|
||||
"user_guide_pql_step1a": "Write a PromQL query for the metric",
|
||||
"user_guide_pql_step1b": "Format the legends based on labels you want to highlight",
|
||||
"user_guide_pql_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_pql_step2a": "Select the threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_pql_step2b": "Enter the Alert threshold",
|
||||
"user_guide_pql_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_pql_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_pql_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_guide_ch_step1": "Step 1 - Define the metric",
|
||||
"user_guide_ch_step1a": "Write a Clickhouse query for alert evaluation. Follow <0>this tutorial</0> to learn about query format and supported vars.",
|
||||
"user_guide_ch_step1b": "Format the legends based on labels you want to highlight in the preview chart",
|
||||
"user_guide_ch_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_ch_step2a": "Select the threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_ch_step2b": "Enter the Alert threshold",
|
||||
"user_guide_ch_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_ch_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_ch_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_tooltip_more_help": "More details on how to create alerts",
|
||||
"choose_alert_type": "Choose a type for the alert:",
|
||||
"metric_based_alert": "Metric based Alert",
|
||||
"metric_based_alert_desc": "Send a notification when a condition occurs in the metric data",
|
||||
"log_based_alert": "Log-based Alert",
|
||||
"log_based_alert_desc": "Send a notification when a condition occurs in the logs data.",
|
||||
"traces_based_alert": "Trace-based Alert",
|
||||
"traces_based_alert_desc": "Send a notification when a condition occurs in the traces data.",
|
||||
"exceptions_based_alert": "Exceptions-based Alert",
|
||||
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data."
|
||||
"target_missing": "Please enter a threshold to proceed",
|
||||
"rule_test_fired": "Test notification sent successfully",
|
||||
"no_alerts_found": "No alerts found during the evaluation. This happens when rule condition is unsatisfied. You may adjust the rule threshold and retry.",
|
||||
"button_testrule": "Test Notification",
|
||||
"label_channel_select": "Notification Channels",
|
||||
"placeholder_channel_select": "select one or more channels",
|
||||
"channel_select_tooltip": "Leave empty to send this alert on all the configured channels",
|
||||
"preview_chart_unexpected_error": "An unexpeced error occurred updating the chart, please check your query.",
|
||||
"preview_chart_threshold_label": "Threshold",
|
||||
"placeholder_label_key_pair": "Click here to enter a label (key value pairs)",
|
||||
"button_yes": "Yes",
|
||||
"button_no": "No",
|
||||
"remove_label_confirm": "This action will remove all the labels. Do you want to proceed?",
|
||||
"remove_label_success": "Labels cleared",
|
||||
"alert_form_step1": "Step 1 - Define the metric",
|
||||
"alert_form_step2": "Step 2 - Define Alert Conditions",
|
||||
"alert_form_step3": "Step 3 - Alert Configuration",
|
||||
"metric_query_max_limit": "Can not create query. You can create maximum of 5 queries",
|
||||
"confirm_save_title": "Save Changes",
|
||||
"confirm_save_content_part1": "Your alert built with",
|
||||
"confirm_save_content_part2": "query will be saved. Press OK to confirm.",
|
||||
"unexpected_error": "Sorry, an unexpected error occurred. Please contact your admin",
|
||||
"rule_created": "Rule created successfully",
|
||||
"rule_edited": "Rule edited successfully",
|
||||
"expression_missing": "expression is missing in {{where}}",
|
||||
"metricname_missing": "metric name is missing in {{where}}",
|
||||
"condition_required": "at least one metric condition is required",
|
||||
"alertname_required": "alert name is required",
|
||||
"promql_required": "promql expression is required when query format is set to PromQL",
|
||||
"chquery_required": "query is required when query format is set to ClickHouse",
|
||||
"button_savechanges": "Save Rule",
|
||||
"button_createrule": "Create Rule",
|
||||
"button_returntorules": "Return to rules",
|
||||
"button_cancelchanges": "Cancel",
|
||||
"button_discard": "Discard",
|
||||
"text_condition1": "Send a notification when the metric is",
|
||||
"text_condition2": "the threshold",
|
||||
"text_condition3": "during the last",
|
||||
"option_5min": "5 mins",
|
||||
"option_10min": "10 mins",
|
||||
"option_15min": "15 mins",
|
||||
"option_60min": "60 mins",
|
||||
"option_4hours": "4 hours",
|
||||
"option_24hours": "24 hours",
|
||||
"field_threshold": "Alert Threshold",
|
||||
"option_allthetimes": "all the times",
|
||||
"option_atleastonce": "at least once",
|
||||
"option_onaverage": "on average",
|
||||
"option_intotal": "in total",
|
||||
"option_above": "above",
|
||||
"option_below": "below",
|
||||
"option_equal": "is equal to",
|
||||
"option_notequal": "not equal to",
|
||||
"button_query": "Query",
|
||||
"button_formula": "Formula",
|
||||
"tab_qb": "Query Builder",
|
||||
"tab_promql": "PromQL",
|
||||
"tab_chquery": "ClickHouse Query",
|
||||
"title_confirm": "Confirm",
|
||||
"button_ok": "Yes",
|
||||
"button_cancel": "No",
|
||||
"field_promql_expr": "PromQL Expression",
|
||||
"field_alert_name": "Alert Name",
|
||||
"field_alert_desc": "Alert Description",
|
||||
"field_labels": "Labels",
|
||||
"field_severity": "Severity",
|
||||
"option_critical": "Critical",
|
||||
"option_error": "Error",
|
||||
"option_warning": "Warning",
|
||||
"option_info": "Info",
|
||||
"user_guide_headline": "Steps to create an Alert",
|
||||
"user_guide_qb_step1": "Step 1 - Define the metric",
|
||||
"user_guide_qb_step1a": "Choose a metric which you want to create an alert on",
|
||||
"user_guide_qb_step1b": "Filter it based on WHERE field or GROUPBY if needed",
|
||||
"user_guide_qb_step1c": "Apply an aggregatiion function like COUNT, SUM, etc. or choose NOOP to plot the raw metric",
|
||||
"user_guide_qb_step1d": "Create a formula based on Queries if needed",
|
||||
"user_guide_qb_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_qb_step2a": "Select the evaluation interval, threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_qb_step2b": "Enter the Alert threshold",
|
||||
"user_guide_qb_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_qb_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_qb_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_guide_pql_step1": "Step 1 - Define the metric",
|
||||
"user_guide_pql_step1a": "Write a PromQL query for the metric",
|
||||
"user_guide_pql_step1b": "Format the legends based on labels you want to highlight",
|
||||
"user_guide_pql_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_pql_step2a": "Select the threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_pql_step2b": "Enter the Alert threshold",
|
||||
"user_guide_pql_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_pql_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_pql_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_guide_ch_step1": "Step 1 - Define the metric",
|
||||
"user_guide_ch_step1a": "Write a Clickhouse query for alert evaluation. Follow <0>this tutorial</0> to learn about query format and supported vars.",
|
||||
"user_guide_ch_step1b": "Format the legends based on labels you want to highlight in the preview chart",
|
||||
"user_guide_ch_step2": "Step 2 - Define Alert Conditions",
|
||||
"user_guide_ch_step2a": "Select the threshold type and whether you want to alert above/below a value",
|
||||
"user_guide_ch_step2b": "Enter the Alert threshold",
|
||||
"user_guide_ch_step3": "Step 3 -Alert Configuration",
|
||||
"user_guide_ch_step3a": "Set alert severity, name and descriptions",
|
||||
"user_guide_ch_step3b": "Add tags to the alert in the Label field if needed",
|
||||
"user_tooltip_more_help": "More details on how to create alerts",
|
||||
"choose_alert_type": "Choose a type for the alert:",
|
||||
"metric_based_alert": "Metric based Alert",
|
||||
"metric_based_alert_desc": "Send a notification when a condition occurs in the metric data",
|
||||
"log_based_alert": "Log-based Alert",
|
||||
"log_based_alert_desc": "Send a notification when a condition occurs in the logs data.",
|
||||
"traces_based_alert": "Trace-based Alert",
|
||||
"traces_based_alert_desc": "Send a notification when a condition occurs in the traces data.",
|
||||
"exceptions_based_alert": "Exceptions-based Alert",
|
||||
"exceptions_based_alert_desc": "Send a notification when a condition occurs in the exceptions data.",
|
||||
"field_unit": "Threshold unit"
|
||||
}
|
34
frontend/src/api/channels/createMsTeams.ts
Normal file
34
frontend/src/api/channels/createMsTeams.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createMsTeams';
|
||||
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/channels', {
|
||||
name: props.name,
|
||||
msteams_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
webhook_url: props.webhook_url,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
34
frontend/src/api/channels/editMsTeams.ts
Normal file
34
frontend/src/api/channels/editMsTeams.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editMsTeams';
|
||||
|
||||
const editMsTeams = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.put(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
msteams_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
webhook_url: props.webhook_url,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default editMsTeams;
|
34
frontend/src/api/channels/testMsTeams.ts
Normal file
34
frontend/src/api/channels/testMsTeams.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createMsTeams';
|
||||
|
||||
const testMsTeams = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/testChannel', {
|
||||
name: props.name,
|
||||
msteams_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
webhook_url: props.webhook_url,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default testMsTeams;
|
16
frontend/src/api/metrics/ApDex/apDexSettings.ts
Normal file
16
frontend/src/api/metrics/ApDex/apDexSettings.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import axios from 'api';
|
||||
import {
|
||||
ApDexPayloadAndSettingsProps,
|
||||
SetApDexPayloadProps,
|
||||
} from 'types/api/metrics/getApDex';
|
||||
|
||||
export const setApDexSettings = async ({
|
||||
servicename,
|
||||
threshold,
|
||||
excludeStatusCode,
|
||||
}: ApDexPayloadAndSettingsProps): Promise<SetApDexPayloadProps> =>
|
||||
axios.post('/settings/apdex', {
|
||||
servicename,
|
||||
threshold,
|
||||
excludeStatusCode,
|
||||
});
|
8
frontend/src/api/metrics/ApDex/getApDexSettings.ts
Normal file
8
frontend/src/api/metrics/ApDex/getApDexSettings.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { ApDexPayloadAndSettingsProps } from 'types/api/metrics/getApDex';
|
||||
|
||||
export const getApDexSettings = (
|
||||
servicename: string,
|
||||
): Promise<AxiosResponse<ApDexPayloadAndSettingsProps[]>> =>
|
||||
axios.get(`/settings/apdex?services=${servicename}`);
|
8
frontend/src/api/metrics/ApDex/getMetricMeta.ts
Normal file
8
frontend/src/api/metrics/ApDex/getMetricMeta.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { MetricMetaProps } from 'types/api/metrics/getApDex';
|
||||
|
||||
export const getMetricMeta = (
|
||||
metricName: string,
|
||||
): Promise<AxiosResponse<MetricMetaProps>> =>
|
||||
axios.get(`/metric_meta?metricName=${metricName}`);
|
@ -3,7 +3,6 @@ import {
|
||||
BarElement,
|
||||
CategoryScale,
|
||||
Chart,
|
||||
ChartType,
|
||||
Decimation,
|
||||
Filler,
|
||||
Legend,
|
||||
@ -18,6 +17,7 @@ import {
|
||||
Tooltip,
|
||||
} from 'chart.js';
|
||||
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||
import { generateGridTitle } from 'container/GridPanelSwitch/utils';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import {
|
||||
@ -26,6 +26,7 @@ import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
|
||||
@ -83,6 +84,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
||||
const nearestDatasetIndex = useRef<null | number>(null);
|
||||
const chartRef = useRef<HTMLCanvasElement>(null);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const gridTitle = useMemo(() => generateGridTitle(title), [title]);
|
||||
|
||||
const currentTheme = isDarkMode ? 'dark' : 'light';
|
||||
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
|
||||
@ -119,7 +121,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
||||
const options: CustomChartOptions = getGraphOptions(
|
||||
animate,
|
||||
staticLine,
|
||||
title,
|
||||
gridTitle,
|
||||
nearestDatasetIndex,
|
||||
yAxisUnit,
|
||||
onDragSelect,
|
||||
@ -154,7 +156,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
||||
}, [
|
||||
animate,
|
||||
staticLine,
|
||||
title,
|
||||
gridTitle,
|
||||
yAxisUnit,
|
||||
onDragSelect,
|
||||
dragSelectColor,
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
ChartType,
|
||||
TimeUnit,
|
||||
} from 'chart.js';
|
||||
import { ForwardedRef } from 'react';
|
||||
import { ForwardedRef, ReactNode } from 'react';
|
||||
|
||||
import {
|
||||
dragSelectPluginId,
|
||||
@ -49,7 +49,7 @@ export interface GraphProps {
|
||||
animate?: boolean;
|
||||
type: ChartType;
|
||||
data: Chart['data'];
|
||||
title?: string;
|
||||
title?: ReactNode;
|
||||
isStacked?: boolean;
|
||||
onClickHandler?: GraphOnClickHandler;
|
||||
name: string;
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { grey } from '@ant-design/colors';
|
||||
import { QuestionCircleFilled } from '@ant-design/icons';
|
||||
import { blue, grey } from '@ant-design/colors';
|
||||
import {
|
||||
QuestionCircleFilled,
|
||||
QuestionCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Tooltip } from 'antd';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
@ -7,7 +10,12 @@ import { useMemo } from 'react';
|
||||
|
||||
import { style } from './styles';
|
||||
|
||||
function TextToolTip({ text, url }: TextToolTipProps): JSX.Element {
|
||||
function TextToolTip({
|
||||
text,
|
||||
url,
|
||||
useFilledIcon = true,
|
||||
urlText,
|
||||
}: TextToolTipProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const overlay = useMemo(
|
||||
@ -16,12 +24,12 @@ function TextToolTip({ text, url }: TextToolTipProps): JSX.Element {
|
||||
{`${text} `}
|
||||
{url && (
|
||||
<a href={url} rel="noopener noreferrer" target="_blank">
|
||||
here
|
||||
{urlText || 'here'}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
[text, url],
|
||||
[text, url, urlText],
|
||||
);
|
||||
|
||||
const iconStyle = useMemo(
|
||||
@ -32,19 +40,35 @@ function TextToolTip({ text, url }: TextToolTipProps): JSX.Element {
|
||||
[isDarkMode],
|
||||
);
|
||||
|
||||
const iconOutlinedStyle = useMemo(
|
||||
() => ({
|
||||
...style,
|
||||
color: isDarkMode ? themeColors.navyBlue : blue[0],
|
||||
}),
|
||||
[isDarkMode],
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip overlay={overlay}>
|
||||
<QuestionCircleFilled style={iconStyle} />
|
||||
{useFilledIcon ? (
|
||||
<QuestionCircleFilled style={iconStyle} />
|
||||
) : (
|
||||
<QuestionCircleOutlined style={iconOutlinedStyle} />
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
TextToolTip.defaultProps = {
|
||||
url: '',
|
||||
urlText: '',
|
||||
useFilledIcon: true,
|
||||
};
|
||||
interface TextToolTipProps {
|
||||
url?: string;
|
||||
text: string;
|
||||
useFilledIcon?: boolean;
|
||||
urlText?: string;
|
||||
}
|
||||
|
||||
export default TextToolTip;
|
||||
|
31
frontend/src/components/Upgrade/UpgradePrompt.tsx
Normal file
31
frontend/src/components/Upgrade/UpgradePrompt.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { Alert, Space } from 'antd';
|
||||
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
|
||||
|
||||
type UpgradePromptProps = {
|
||||
title?: string;
|
||||
};
|
||||
|
||||
function UpgradePrompt({ title }: UpgradePromptProps): JSX.Element {
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Alert
|
||||
message={title}
|
||||
description={
|
||||
<div>
|
||||
This feature is available for paid plans only.{' '}
|
||||
<a href={SIGNOZ_UPGRADE_PLAN_URL} target="_blank" rel="noreferrer">
|
||||
Click here
|
||||
</a>{' '}
|
||||
to Upgrade
|
||||
</div>
|
||||
}
|
||||
type="warning"
|
||||
/>{' '}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
UpgradePrompt.defaultProps = {
|
||||
title: 'Upgrade to a Paid Plan',
|
||||
};
|
||||
export default UpgradePrompt;
|
5
frontend/src/constants/apDex.ts
Normal file
5
frontend/src/constants/apDex.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const apDexToolTipText =
|
||||
"Apdex is a way to measure your users' satisfaction with the response time of your web service. It's represented as a score from 0-1.";
|
||||
export const apDexToolTipUrl =
|
||||
'https://signoz.io/docs/userguide/metrics/#apdex?utm_source=product&utm_medium=frontend&utm_campaign=apdex';
|
||||
export const apDexToolTipUrlText = 'Learn more about Apdex.';
|
@ -1,6 +1,12 @@
|
||||
// keep this consistent with backend constants.go
|
||||
export enum FeatureKeys {
|
||||
SSO = 'SSO',
|
||||
ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
|
||||
BASIC_PLAN = 'BASIC_PLAN',
|
||||
ALERT_CHANNEL_SLACK = 'ALERT_CHANNEL_SLACK',
|
||||
ALERT_CHANNEL_WEBHOOK = 'ALERT_CHANNEL_WEBHOOK',
|
||||
ALERT_CHANNEL_PAGERDUTY = 'ALERT_CHANNEL_PAGERDUTY',
|
||||
ALERT_CHANNEL_MSTEAMS = 'ALERT_CHANNEL_MSTEAMS',
|
||||
DurationSort = 'DurationSort',
|
||||
TimestampSort = 'TimestampSort',
|
||||
SMART_TRACE_DETAIL = 'SMART_TRACE_DETAIL',
|
||||
@ -9,4 +15,5 @@ export enum FeatureKeys {
|
||||
QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
|
||||
DISABLE_UPSELL = 'DISABLE_UPSELL',
|
||||
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
|
||||
OSS = 'OSS',
|
||||
}
|
||||
|
@ -63,10 +63,16 @@ export const ValidatePagerChannel = (p: PagerChannel): string => {
|
||||
return '';
|
||||
};
|
||||
|
||||
export type ChannelType = 'slack' | 'email' | 'webhook' | 'pagerduty';
|
||||
export type ChannelType =
|
||||
| 'slack'
|
||||
| 'email'
|
||||
| 'webhook'
|
||||
| 'pagerduty'
|
||||
| 'msteams';
|
||||
export const SlackType: ChannelType = 'slack';
|
||||
export const WebhookType: ChannelType = 'webhook';
|
||||
export const PagerType: ChannelType = 'pagerduty';
|
||||
export const MsTeamsType: ChannelType = 'msteams';
|
||||
|
||||
// LabelFilterStatement will be used for preparing filter conditions / matchers
|
||||
export interface LabelFilterStatement {
|
||||
@ -81,3 +87,9 @@ export interface LabelFilterStatement {
|
||||
// filter value
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface MsTeamsChannel extends Channel {
|
||||
webhook_url?: string;
|
||||
title?: string;
|
||||
text?: string;
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Form } from 'antd';
|
||||
import createMsTeamsApi from 'api/channels/createMsTeams';
|
||||
import createPagerApi from 'api/channels/createPager';
|
||||
import createSlackApi from 'api/channels/createSlack';
|
||||
import createWebhookApi from 'api/channels/createWebhook';
|
||||
import testMsTeamsApi from 'api/channels/testMsTeams';
|
||||
import testPagerApi from 'api/channels/testPager';
|
||||
import testSlackApi from 'api/channels/testSlack';
|
||||
import testWebhookApi from 'api/channels/testWebhook';
|
||||
@ -14,6 +16,8 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
ChannelType,
|
||||
MsTeamsChannel,
|
||||
MsTeamsType,
|
||||
PagerChannel,
|
||||
PagerType,
|
||||
SlackChannel,
|
||||
@ -33,7 +37,7 @@ function CreateAlertChannels({
|
||||
const [formInstance] = Form.useForm();
|
||||
|
||||
const [selectedConfig, setSelectedConfig] = useState<
|
||||
Partial<SlackChannel & WebhookChannel & PagerChannel>
|
||||
Partial<SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel>
|
||||
>({
|
||||
text: `{{ range .Alerts -}}
|
||||
*Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
||||
@ -102,9 +106,7 @@ function CreateAlertChannels({
|
||||
message: 'Success',
|
||||
description: t('channel_creation_done'),
|
||||
});
|
||||
setTimeout(() => {
|
||||
history.replace(ROUTES.SETTINGS);
|
||||
}, 2000);
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
@ -165,9 +167,7 @@ function CreateAlertChannels({
|
||||
message: 'Success',
|
||||
description: t('channel_creation_done'),
|
||||
});
|
||||
setTimeout(() => {
|
||||
history.replace(ROUTES.SETTINGS);
|
||||
}, 2000);
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
@ -222,9 +222,7 @@ function CreateAlertChannels({
|
||||
message: 'Success',
|
||||
description: t('channel_creation_done'),
|
||||
});
|
||||
setTimeout(() => {
|
||||
history.replace(ROUTES.SETTINGS);
|
||||
}, 2000);
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
@ -241,26 +239,71 @@ function CreateAlertChannels({
|
||||
setSavingState(false);
|
||||
}, [t, notifications, preparePagerRequest]);
|
||||
|
||||
const prepareMsTeamsRequest = useCallback(
|
||||
() => ({
|
||||
webhook_url: selectedConfig?.webhook_url || '',
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: true,
|
||||
text: selectedConfig?.text || '',
|
||||
title: selectedConfig?.title || '',
|
||||
}),
|
||||
[selectedConfig],
|
||||
);
|
||||
|
||||
const onMsTeamsHandler = useCallback(async () => {
|
||||
setSavingState(true);
|
||||
|
||||
try {
|
||||
const response = await createMsTeamsApi(prepareMsTeamsRequest());
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: t('channel_creation_done'),
|
||||
});
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: response.error || t('channel_creation_failed'),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('channel_creation_failed'),
|
||||
});
|
||||
}
|
||||
setSavingState(false);
|
||||
}, [prepareMsTeamsRequest, t, notifications]);
|
||||
|
||||
const onSaveHandler = useCallback(
|
||||
async (value: ChannelType) => {
|
||||
switch (value) {
|
||||
case SlackType:
|
||||
onSlackHandler();
|
||||
break;
|
||||
case WebhookType:
|
||||
onWebhookHandler();
|
||||
break;
|
||||
case PagerType:
|
||||
onPagerHandler();
|
||||
break;
|
||||
default:
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('selected_channel_invalid'),
|
||||
});
|
||||
const functionMapper = {
|
||||
[SlackType]: onSlackHandler,
|
||||
[WebhookType]: onWebhookHandler,
|
||||
[PagerType]: onPagerHandler,
|
||||
[MsTeamsType]: onMsTeamsHandler,
|
||||
};
|
||||
const functionToCall = functionMapper[value];
|
||||
|
||||
if (functionToCall) {
|
||||
functionToCall();
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('selected_channel_invalid'),
|
||||
});
|
||||
}
|
||||
},
|
||||
[onSlackHandler, t, onPagerHandler, onWebhookHandler, notifications],
|
||||
[
|
||||
onSlackHandler,
|
||||
onWebhookHandler,
|
||||
onPagerHandler,
|
||||
onMsTeamsHandler,
|
||||
notifications,
|
||||
t,
|
||||
],
|
||||
);
|
||||
|
||||
const performChannelTest = useCallback(
|
||||
@ -282,6 +325,10 @@ function CreateAlertChannels({
|
||||
request = preparePagerRequest();
|
||||
if (request) response = await testPagerApi(request);
|
||||
break;
|
||||
case MsTeamsType:
|
||||
request = prepareMsTeamsRequest();
|
||||
response = await testMsTeamsApi(request);
|
||||
break;
|
||||
default:
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
@ -315,6 +362,7 @@ function CreateAlertChannels({
|
||||
t,
|
||||
preparePagerRequest,
|
||||
prepareSlackRequest,
|
||||
prepareMsTeamsRequest,
|
||||
notifications,
|
||||
],
|
||||
);
|
||||
|
@ -40,6 +40,7 @@ export const alertDefaults: AlertDef = {
|
||||
},
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
unit: undefined,
|
||||
},
|
||||
op: defaultCompareOp,
|
||||
matchType: defaultMatchType,
|
||||
@ -69,6 +70,7 @@ export const logAlertDefaults: AlertDef = {
|
||||
},
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
unit: undefined,
|
||||
},
|
||||
op: defaultCompareOp,
|
||||
matchType: '4',
|
||||
@ -99,6 +101,7 @@ export const traceAlertDefaults: AlertDef = {
|
||||
},
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
unit: undefined,
|
||||
},
|
||||
op: defaultCompareOp,
|
||||
matchType: '4',
|
||||
@ -129,6 +132,7 @@ export const exceptionAlertDefaults: AlertDef = {
|
||||
},
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
unit: undefined,
|
||||
},
|
||||
op: defaultCompareOp,
|
||||
matchType: '4',
|
||||
|
@ -1,13 +1,17 @@
|
||||
import { Form } from 'antd';
|
||||
import editMsTeamsApi from 'api/channels/editMsTeams';
|
||||
import editPagerApi from 'api/channels/editPager';
|
||||
import editSlackApi from 'api/channels/editSlack';
|
||||
import editWebhookApi from 'api/channels/editWebhook';
|
||||
import testMsTeamsApi from 'api/channels/testMsTeams';
|
||||
import testPagerApi from 'api/channels/testPager';
|
||||
import testSlackApi from 'api/channels/testSlack';
|
||||
import testWebhookApi from 'api/channels/testWebhook';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
ChannelType,
|
||||
MsTeamsChannel,
|
||||
MsTeamsType,
|
||||
PagerChannel,
|
||||
PagerType,
|
||||
SlackChannel,
|
||||
@ -31,7 +35,7 @@ function EditAlertChannels({
|
||||
|
||||
const [formInstance] = Form.useForm();
|
||||
const [selectedConfig, setSelectedConfig] = useState<
|
||||
Partial<SlackChannel & WebhookChannel & PagerChannel>
|
||||
Partial<SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel>
|
||||
>({
|
||||
...initialValue,
|
||||
});
|
||||
@ -81,9 +85,7 @@ function EditAlertChannels({
|
||||
description: t('channel_edit_done'),
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
history.replace(ROUTES.SETTINGS);
|
||||
}, 2000);
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
@ -136,9 +138,7 @@ function EditAlertChannels({
|
||||
description: t('channel_edit_done'),
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
history.replace(ROUTES.SETTINGS);
|
||||
}, 2000);
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
} else {
|
||||
showError(response.error || t('channel_edit_failed'));
|
||||
}
|
||||
@ -183,9 +183,7 @@ function EditAlertChannels({
|
||||
description: t('channel_edit_done'),
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
history.replace(ROUTES.SETTINGS);
|
||||
}, 2000);
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
@ -195,6 +193,48 @@ function EditAlertChannels({
|
||||
setSavingState(false);
|
||||
}, [preparePagerRequest, notifications, selectedConfig, t]);
|
||||
|
||||
const prepareMsTeamsRequest = useCallback(
|
||||
() => ({
|
||||
webhook_url: selectedConfig?.webhook_url || '',
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: true,
|
||||
text: selectedConfig?.text || '',
|
||||
title: selectedConfig?.title || '',
|
||||
id,
|
||||
}),
|
||||
[id, selectedConfig],
|
||||
);
|
||||
|
||||
const onMsTeamsEditHandler = useCallback(async () => {
|
||||
setSavingState(true);
|
||||
|
||||
if (selectedConfig?.webhook_url === '') {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('webhook_url_required'),
|
||||
});
|
||||
setSavingState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await editMsTeamsApi(prepareMsTeamsRequest());
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: t('channel_edit_done'),
|
||||
});
|
||||
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: response.error || t('channel_edit_failed'),
|
||||
});
|
||||
}
|
||||
setSavingState(false);
|
||||
}, [prepareMsTeamsRequest, t, notifications, selectedConfig]);
|
||||
|
||||
const onSaveHandler = useCallback(
|
||||
(value: ChannelType) => {
|
||||
if (value === SlackType) {
|
||||
@ -203,9 +243,16 @@ function EditAlertChannels({
|
||||
onWebhookEditHandler();
|
||||
} else if (value === PagerType) {
|
||||
onPagerEditHandler();
|
||||
} else if (value === MsTeamsType) {
|
||||
onMsTeamsEditHandler();
|
||||
}
|
||||
},
|
||||
[onSlackEditHandler, onWebhookEditHandler, onPagerEditHandler],
|
||||
[
|
||||
onSlackEditHandler,
|
||||
onWebhookEditHandler,
|
||||
onPagerEditHandler,
|
||||
onMsTeamsEditHandler,
|
||||
],
|
||||
);
|
||||
|
||||
const performChannelTest = useCallback(
|
||||
@ -227,6 +274,10 @@ function EditAlertChannels({
|
||||
request = preparePagerRequest();
|
||||
if (request) response = await testPagerApi(request);
|
||||
break;
|
||||
case MsTeamsType:
|
||||
request = prepareMsTeamsRequest();
|
||||
if (request) response = await testMsTeamsApi(request);
|
||||
break;
|
||||
default:
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
@ -260,6 +311,7 @@ function EditAlertChannels({
|
||||
prepareWebhookRequest,
|
||||
preparePagerRequest,
|
||||
prepareSlackRequest,
|
||||
prepareMsTeamsRequest,
|
||||
notifications,
|
||||
],
|
||||
);
|
||||
|
@ -0,0 +1,57 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MsTeamsChannel } from '../../CreateAlertChannels/config';
|
||||
|
||||
function MsTeams({ setSelectedConfig }: MsTeamsProps): JSX.Element {
|
||||
const { t } = useTranslation('channels');
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="webhook_url" label={t('field_webhook_url')}>
|
||||
<Input
|
||||
onChange={(event): void => {
|
||||
setSelectedConfig((value) => ({
|
||||
...value,
|
||||
webhook_url: event.target.value,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="title" label={t('field_slack_title')}>
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
// value={`[{{ .Status | toUpper }}{{ if eq .Status \"firing\" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}\n{{- if gt (len .CommonLabels) (len .GroupLabels) -}}\n{{\" \"}}(\n{{- with .CommonLabels.Remove .GroupLabels.Names }}\n {{- range $index, $label := .SortedPairs -}}\n {{ if $index }}, {{ end }}\n {{- $label.Name }}=\"{{ $label.Value -}}\"\n {{- end }}\n{{- end -}}\n)\n{{- end }}`}
|
||||
onChange={(event): void =>
|
||||
setSelectedConfig((value) => ({
|
||||
...value,
|
||||
title: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="text" label={t('field_slack_description')}>
|
||||
<Input.TextArea
|
||||
onChange={(event): void =>
|
||||
setSelectedConfig((value) => ({
|
||||
...value,
|
||||
text: event.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder={t('placeholder_slack_description')}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface MsTeamsProps {
|
||||
setSelectedConfig: React.Dispatch<
|
||||
React.SetStateAction<Partial<MsTeamsChannel>>
|
||||
>;
|
||||
}
|
||||
|
||||
export default MsTeams;
|
@ -1,8 +1,11 @@
|
||||
import { Form, FormInstance, Input, Select, Typography } from 'antd';
|
||||
import { Store } from 'antd/lib/form/interface';
|
||||
import UpgradePrompt from 'components/Upgrade/UpgradePrompt';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
ChannelType,
|
||||
MsTeamsType,
|
||||
PagerChannel,
|
||||
PagerType,
|
||||
SlackChannel,
|
||||
@ -10,18 +13,18 @@ import {
|
||||
WebhookChannel,
|
||||
WebhookType,
|
||||
} from 'container/CreateAlertChannels/config';
|
||||
import useFeatureFlags from 'hooks/useFeatureFlag';
|
||||
import { isFeatureKeys } from 'hooks/useFeatureFlag/utils';
|
||||
import history from 'lib/history';
|
||||
import { Dispatch, ReactElement, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import MsTeamsSettings from './Settings/MsTeams';
|
||||
import PagerSettings from './Settings/Pager';
|
||||
import SlackSettings from './Settings/Slack';
|
||||
import WebhookSettings from './Settings/Webhook';
|
||||
import { Button } from './styles';
|
||||
|
||||
const { Option } = Select;
|
||||
const { Title } = Typography;
|
||||
|
||||
function FormAlertChannels({
|
||||
formInstance,
|
||||
type,
|
||||
@ -36,8 +39,27 @@ function FormAlertChannels({
|
||||
editing = false,
|
||||
}: FormAlertChannelsProps): JSX.Element {
|
||||
const { t } = useTranslation('channels');
|
||||
const isUserOnEEPlan = useFeatureFlags(FeatureKeys.ENTERPRISE_PLAN);
|
||||
|
||||
const feature = `ALERT_CHANNEL_${type.toUpperCase()}`;
|
||||
|
||||
const hasFeature = useFeatureFlags(
|
||||
isFeatureKeys(feature) ? feature : FeatureKeys.ALERT_CHANNEL_SLACK,
|
||||
);
|
||||
|
||||
const isOssFeature = useFeatureFlags(FeatureKeys.OSS);
|
||||
|
||||
const renderSettings = (): ReactElement | null => {
|
||||
if (
|
||||
// for ee plan
|
||||
!isOssFeature?.active &&
|
||||
(!hasFeature || !hasFeature.active) &&
|
||||
type === 'msteams'
|
||||
) {
|
||||
// channel type is not available for users plan
|
||||
return <UpgradePrompt />;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case SlackType:
|
||||
return <SlackSettings setSelectedConfig={setSelectedConfig} />;
|
||||
@ -45,14 +67,16 @@ function FormAlertChannels({
|
||||
return <WebhookSettings setSelectedConfig={setSelectedConfig} />;
|
||||
case PagerType:
|
||||
return <PagerSettings setSelectedConfig={setSelectedConfig} />;
|
||||
|
||||
case MsTeamsType:
|
||||
return <MsTeamsSettings setSelectedConfig={setSelectedConfig} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title level={3}>{title}</Title>
|
||||
<Typography.Title level={3}>{title}</Typography.Title>
|
||||
|
||||
<Form initialValues={initialValue} layout="vertical" form={formInstance}>
|
||||
<Form.Item label={t('field_channel_name')} labelAlign="left" name="name">
|
||||
@ -69,15 +93,22 @@ function FormAlertChannels({
|
||||
|
||||
<Form.Item label={t('field_channel_type')} labelAlign="left" name="type">
|
||||
<Select disabled={editing} onChange={onTypeChangeHandler} value={type}>
|
||||
<Option value="slack" key="slack">
|
||||
<Select.Option value="slack" key="slack">
|
||||
Slack
|
||||
</Option>
|
||||
<Option value="webhook" key="webhook">
|
||||
</Select.Option>
|
||||
<Select.Option value="webhook" key="webhook">
|
||||
Webhook
|
||||
</Option>
|
||||
<Option value="pagerduty" key="pagerduty">
|
||||
</Select.Option>
|
||||
<Select.Option value="pagerduty" key="pagerduty">
|
||||
Pagerduty
|
||||
</Option>
|
||||
</Select.Option>
|
||||
{!isOssFeature?.active && (
|
||||
<Select.Option value="msteams" key="msteams">
|
||||
<div>
|
||||
Microsoft Teams {!isUserOnEEPlan && '(Supported in Paid Plans Only)'}{' '}
|
||||
</div>
|
||||
</Select.Option>
|
||||
)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
@ -85,7 +116,7 @@ function FormAlertChannels({
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
disabled={savingState}
|
||||
disabled={savingState || !hasFeature}
|
||||
loading={savingState}
|
||||
type="primary"
|
||||
onClick={(): void => onSaveHandler(type)}
|
||||
@ -93,7 +124,7 @@ function FormAlertChannels({
|
||||
{t('button_save_channel')}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={testingState}
|
||||
disabled={testingState || !hasFeature}
|
||||
loading={testingState}
|
||||
onClick={(): void => onTestHandler(type)}
|
||||
>
|
||||
|
98
frontend/src/container/FormAlertRules/ChartPreview/config.ts
Normal file
98
frontend/src/container/FormAlertRules/ChartPreview/config.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import {
|
||||
DataFormats,
|
||||
DataRateFormats,
|
||||
MiscellaneousFormats,
|
||||
ThroughputFormats,
|
||||
TimeFormats,
|
||||
} from 'container/NewWidget/RightContainer/types';
|
||||
|
||||
export const dataFormatConfig: Record<DataFormats, number> = {
|
||||
[DataFormats.BytesIEC]: 1,
|
||||
[DataFormats.BytesSI]: 1,
|
||||
[DataFormats.BitsIEC]: 1 / 8,
|
||||
[DataFormats.BitsSI]: 1 / 8,
|
||||
[DataFormats.KibiBytes]: 1024,
|
||||
[DataFormats.KiloBytes]: 1000,
|
||||
[DataFormats.MebiBytes]: 1024 ** 2,
|
||||
[DataFormats.MegaBytes]: 1000 ** 2,
|
||||
[DataFormats.GibiBytes]: 1024 ** 3,
|
||||
[DataFormats.GigaBytes]: 1000 ** 3,
|
||||
[DataFormats.TebiBytes]: 1024 ** 4,
|
||||
[DataFormats.TeraBytes]: 1000 ** 4,
|
||||
[DataFormats.PebiBytes]: 1024 ** 5,
|
||||
[DataFormats.PetaBytes]: 1000 ** 5,
|
||||
};
|
||||
|
||||
export const throughputConfig: Record<ThroughputFormats, number> = {
|
||||
[ThroughputFormats.CountsPerSec]: 1,
|
||||
[ThroughputFormats.OpsPerSec]: 1,
|
||||
[ThroughputFormats.RequestsPerSec]: 1,
|
||||
[ThroughputFormats.ReadsPerSec]: 1,
|
||||
[ThroughputFormats.WritesPerSec]: 1,
|
||||
[ThroughputFormats.IOOpsPerSec]: 1,
|
||||
[ThroughputFormats.CountsPerMin]: 1 / 60,
|
||||
[ThroughputFormats.OpsPerMin]: 1 / 60,
|
||||
[ThroughputFormats.ReadsPerMin]: 1 / 60,
|
||||
[ThroughputFormats.WritesPerMin]: 1 / 60,
|
||||
};
|
||||
|
||||
export const timeUnitsConfig: Record<TimeFormats, number> = {
|
||||
[TimeFormats.Hertz]: 1,
|
||||
[TimeFormats.Nanoseconds]: 1e-9,
|
||||
[TimeFormats.Microseconds]: 1e-6,
|
||||
[TimeFormats.Milliseconds]: 1e-3,
|
||||
[TimeFormats.Seconds]: 1,
|
||||
[TimeFormats.Minutes]: 60,
|
||||
[TimeFormats.Hours]: 3600,
|
||||
[TimeFormats.Days]: 86400,
|
||||
[TimeFormats.DurationMs]: 1e-3,
|
||||
[TimeFormats.DurationS]: 1,
|
||||
[TimeFormats.DurationHms]: 3600,
|
||||
[TimeFormats.DurationDhms]: 86400,
|
||||
[TimeFormats.Timeticks]: 1e-3,
|
||||
[TimeFormats.ClockMs]: 1e-3,
|
||||
[TimeFormats.ClockS]: 1,
|
||||
};
|
||||
|
||||
export const dataRateUnitsConfig: Record<DataRateFormats, number> = {
|
||||
[DataRateFormats.PacketsPerSec]: 1,
|
||||
[DataRateFormats.BytesPerSecIEC]: dataFormatConfig[DataFormats.BytesIEC],
|
||||
[DataRateFormats.BytesPerSecSI]: dataFormatConfig[DataFormats.BytesSI],
|
||||
[DataRateFormats.BitsPerSecIEC]: dataFormatConfig[DataFormats.BitsIEC],
|
||||
[DataRateFormats.BitsPerSecSI]: dataFormatConfig[DataFormats.BitsSI],
|
||||
[DataRateFormats.KibiBytesPerSec]: dataFormatConfig[DataFormats.KibiBytes],
|
||||
[DataRateFormats.KibiBitsPerSec]: dataFormatConfig[DataFormats.KibiBytes] * 8,
|
||||
[DataRateFormats.KiloBytesPerSec]: dataFormatConfig[DataFormats.KiloBytes],
|
||||
[DataRateFormats.KiloBitsPerSec]: dataFormatConfig[DataFormats.KiloBytes] * 8,
|
||||
[DataRateFormats.MebiBytesPerSec]: dataFormatConfig[DataFormats.MebiBytes],
|
||||
[DataRateFormats.MebiBitsPerSec]: dataFormatConfig[DataFormats.MebiBytes] * 8,
|
||||
[DataRateFormats.MegaBytesPerSec]: dataFormatConfig[DataFormats.MegaBytes],
|
||||
[DataRateFormats.MegaBitsPerSec]: dataFormatConfig[DataFormats.MegaBytes] * 8,
|
||||
[DataRateFormats.GibiBytesPerSec]: dataFormatConfig[DataFormats.GibiBytes],
|
||||
[DataRateFormats.GibiBitsPerSec]: dataFormatConfig[DataFormats.GibiBytes] * 8,
|
||||
[DataRateFormats.GigaBytesPerSec]: dataFormatConfig[DataFormats.GigaBytes],
|
||||
[DataRateFormats.GigaBitsPerSec]: dataFormatConfig[DataFormats.GigaBytes] * 8,
|
||||
[DataRateFormats.TebiBytesPerSec]: dataFormatConfig[DataFormats.TebiBytes],
|
||||
[DataRateFormats.TebiBitsPerSec]: dataFormatConfig[DataFormats.TebiBytes] * 8,
|
||||
[DataRateFormats.TeraBytesPerSec]: dataFormatConfig[DataFormats.TeraBytes],
|
||||
[DataRateFormats.TeraBitsPerSec]: dataFormatConfig[DataFormats.TeraBytes] * 8,
|
||||
[DataRateFormats.PebiBytesPerSec]: dataFormatConfig[DataFormats.PebiBytes],
|
||||
[DataRateFormats.PebiBitsPerSec]: dataFormatConfig[DataFormats.PebiBytes] * 8,
|
||||
[DataRateFormats.PetaBytesPerSec]: dataFormatConfig[DataFormats.PetaBytes],
|
||||
[DataRateFormats.PetaBitsPerSec]: dataFormatConfig[DataFormats.PetaBytes] * 8,
|
||||
};
|
||||
|
||||
export const miscUnitsConfig: Record<MiscellaneousFormats, number> = {
|
||||
[MiscellaneousFormats.None]: 1,
|
||||
[MiscellaneousFormats.String]: 1,
|
||||
[MiscellaneousFormats.Short]: 1,
|
||||
[MiscellaneousFormats.Percent]: 0.01,
|
||||
[MiscellaneousFormats.PercentUnit]: 1,
|
||||
[MiscellaneousFormats.Humidity]: 1,
|
||||
[MiscellaneousFormats.Decibel]: 1,
|
||||
[MiscellaneousFormats.Hexadecimal0x]: 1,
|
||||
[MiscellaneousFormats.Hexadecimal]: 1,
|
||||
[MiscellaneousFormats.ScientificNotation]: 1,
|
||||
[MiscellaneousFormats.LocaleFormat]: 1,
|
||||
[MiscellaneousFormats.Pixels]: 1,
|
||||
};
|
@ -9,10 +9,12 @@ import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import getChartData from 'lib/getChartData';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertDef } from 'types/api/alerts/def';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
import { ChartContainer, FailedMessageContainer } from './styles';
|
||||
import { covertIntoDataFormats } from './utils';
|
||||
|
||||
export interface ChartPreviewProps {
|
||||
name: string;
|
||||
@ -21,7 +23,7 @@ export interface ChartPreviewProps {
|
||||
selectedTime?: timePreferenceType;
|
||||
selectedInterval?: Time;
|
||||
headline?: JSX.Element;
|
||||
threshold?: number | undefined;
|
||||
alertDef?: AlertDef;
|
||||
userQueryKey?: string;
|
||||
}
|
||||
|
||||
@ -32,18 +34,28 @@ function ChartPreview({
|
||||
selectedTime = 'GLOBAL_TIME',
|
||||
selectedInterval = '5min',
|
||||
headline,
|
||||
threshold,
|
||||
userQueryKey,
|
||||
alertDef,
|
||||
}: ChartPreviewProps): JSX.Element | null {
|
||||
const { t } = useTranslation('alerts');
|
||||
const threshold = alertDef?.condition.target || 0;
|
||||
|
||||
const thresholdValue = covertIntoDataFormats({
|
||||
value: threshold,
|
||||
sourceUnit: alertDef?.condition.targetUnit,
|
||||
targetUnit: query?.unit,
|
||||
});
|
||||
|
||||
const staticLine: StaticLineProps | undefined =
|
||||
threshold !== undefined
|
||||
? {
|
||||
yMin: threshold,
|
||||
yMax: threshold,
|
||||
yMin: thresholdValue,
|
||||
yMax: thresholdValue,
|
||||
borderColor: '#f14',
|
||||
borderWidth: 1,
|
||||
lineText: `${t('preview_chart_threshold_label')} (y=${threshold})`,
|
||||
lineText: `${t('preview_chart_threshold_label')} (y=${thresholdValue} ${
|
||||
query?.unit || ''
|
||||
})`,
|
||||
textColor: '#f14',
|
||||
}
|
||||
: undefined;
|
||||
@ -121,6 +133,7 @@ function ChartPreview({
|
||||
staticLine={staticLine}
|
||||
panelData={queryResponse.data?.payload.data.newResult.data.result || []}
|
||||
query={query || initialQueriesMap.metrics}
|
||||
yAxisUnit={query?.unit}
|
||||
/>
|
||||
)}
|
||||
</ChartContainer>
|
||||
@ -132,8 +145,8 @@ ChartPreview.defaultProps = {
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
selectedInterval: '5min',
|
||||
headline: undefined,
|
||||
threshold: undefined,
|
||||
userQueryKey: '',
|
||||
alertDef: undefined,
|
||||
};
|
||||
|
||||
export default ChartPreview;
|
||||
|
105
frontend/src/container/FormAlertRules/ChartPreview/utils.test.ts
Normal file
105
frontend/src/container/FormAlertRules/ChartPreview/utils.test.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { DataFormats } from 'container/NewWidget/RightContainer/types';
|
||||
|
||||
import { covertIntoDataFormats } from './utils';
|
||||
|
||||
describe('Convert One Unit to another unit', () => {
|
||||
it('should convert from BitsIEC to BytesIEC', () => {
|
||||
const result = covertIntoDataFormats({
|
||||
value: 8,
|
||||
sourceUnit: DataFormats.BitsIEC,
|
||||
targetUnit: DataFormats.BytesIEC,
|
||||
});
|
||||
expect(result).toBe(1);
|
||||
});
|
||||
|
||||
// for KibiBytes to MebiBytes conversion
|
||||
it('should convert from KibiBytes to MebiBytes', () => {
|
||||
const result = covertIntoDataFormats({
|
||||
value: 1024,
|
||||
sourceUnit: DataFormats.KibiBytes,
|
||||
targetUnit: DataFormats.MebiBytes,
|
||||
});
|
||||
expect(result).toBe(1);
|
||||
});
|
||||
|
||||
// for MegaBytes to GigaBytes conversion (SI units)
|
||||
it('should convert from MegaBytes to GigaBytes (SI)', () => {
|
||||
const result = covertIntoDataFormats({
|
||||
value: 1000,
|
||||
sourceUnit: DataFormats.MegaBytes,
|
||||
targetUnit: DataFormats.GigaBytes,
|
||||
});
|
||||
expect(result).toBe(1);
|
||||
});
|
||||
|
||||
// for identity conversion
|
||||
it('should handle identity conversion', () => {
|
||||
const result = covertIntoDataFormats({
|
||||
value: 100,
|
||||
sourceUnit: DataFormats.KibiBytes,
|
||||
targetUnit: DataFormats.KibiBytes,
|
||||
});
|
||||
expect(result).toBe(100);
|
||||
});
|
||||
|
||||
// BytesIEC to BitsIEC conversion
|
||||
it('should convert from BytesIEC to BitsIEC', () => {
|
||||
const result = covertIntoDataFormats({
|
||||
value: 1,
|
||||
sourceUnit: DataFormats.BytesIEC,
|
||||
targetUnit: DataFormats.BitsIEC,
|
||||
});
|
||||
expect(result).toBe(8);
|
||||
});
|
||||
|
||||
// for GibiBytes to TebiBytes conversion
|
||||
it('should convert from GibiBytes to TebiBytes', () => {
|
||||
const result = covertIntoDataFormats({
|
||||
value: 1024,
|
||||
sourceUnit: DataFormats.GibiBytes,
|
||||
targetUnit: DataFormats.TebiBytes,
|
||||
});
|
||||
expect(result).toBe(1);
|
||||
});
|
||||
|
||||
// for GigaBytes to TeraBytes conversion (SI units)
|
||||
it('should convert from GigaBytes to TeraBytes (SI)', () => {
|
||||
const result = covertIntoDataFormats({
|
||||
value: 1000,
|
||||
sourceUnit: DataFormats.GigaBytes,
|
||||
targetUnit: DataFormats.TeraBytes,
|
||||
});
|
||||
expect(result).toBe(1);
|
||||
});
|
||||
|
||||
// for GigaBytes to GibiBytes conversion (cross conversion)
|
||||
it('should convert from GigaBytes to GibiBytes', () => {
|
||||
const result = covertIntoDataFormats({
|
||||
value: 1,
|
||||
sourceUnit: DataFormats.GigaBytes,
|
||||
targetUnit: DataFormats.GibiBytes,
|
||||
});
|
||||
// 1 GB = 0.93132257461548 GiB approximately
|
||||
expect(result).toBeCloseTo(0.93132257461548);
|
||||
});
|
||||
|
||||
// for a large number conversion
|
||||
it('should handle large number conversion from PebiBytes to BitsIEC', () => {
|
||||
const result = covertIntoDataFormats({
|
||||
value: 1,
|
||||
sourceUnit: DataFormats.PebiBytes,
|
||||
targetUnit: DataFormats.BitsIEC,
|
||||
});
|
||||
expect(result).toBe(1 * 1024 ** 5 * 8); // 1 PebiByte = 2^50 Bytes = 2^53 Bits
|
||||
});
|
||||
|
||||
// Negative value conversion
|
||||
it('should handle negative values', () => {
|
||||
const result = covertIntoDataFormats({
|
||||
value: -1,
|
||||
sourceUnit: DataFormats.KibiBytes,
|
||||
targetUnit: DataFormats.BytesIEC,
|
||||
});
|
||||
expect(result).toBe(-1024);
|
||||
});
|
||||
});
|
54
frontend/src/container/FormAlertRules/ChartPreview/utils.ts
Normal file
54
frontend/src/container/FormAlertRules/ChartPreview/utils.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import {
|
||||
BooleanFormats,
|
||||
DataFormats,
|
||||
DataRateFormats,
|
||||
MiscellaneousFormats,
|
||||
ThroughputFormats,
|
||||
TimeFormats,
|
||||
} from 'container/NewWidget/RightContainer/types';
|
||||
|
||||
import {
|
||||
dataFormatConfig,
|
||||
dataRateUnitsConfig,
|
||||
miscUnitsConfig,
|
||||
throughputConfig,
|
||||
timeUnitsConfig,
|
||||
} from './config';
|
||||
|
||||
export function covertIntoDataFormats({
|
||||
value,
|
||||
sourceUnit,
|
||||
targetUnit,
|
||||
}: IUnit): number {
|
||||
if (Object.values(BooleanFormats).includes(sourceUnit as BooleanFormats)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const sourceMultiplier =
|
||||
dataFormatConfig[sourceUnit as DataFormats] ||
|
||||
timeUnitsConfig[sourceUnit as TimeFormats] ||
|
||||
dataRateUnitsConfig[sourceUnit as DataRateFormats] ||
|
||||
miscUnitsConfig[sourceUnit as MiscellaneousFormats] ||
|
||||
throughputConfig[sourceUnit as ThroughputFormats];
|
||||
|
||||
const targetDivider =
|
||||
dataFormatConfig[targetUnit as DataFormats] ||
|
||||
timeUnitsConfig[targetUnit as TimeFormats] ||
|
||||
dataRateUnitsConfig[targetUnit as DataRateFormats] ||
|
||||
miscUnitsConfig[targetUnit as MiscellaneousFormats] ||
|
||||
throughputConfig[sourceUnit as ThroughputFormats];
|
||||
|
||||
const intermediateValue = value * sourceMultiplier;
|
||||
|
||||
const roundedValue = Math.round(intermediateValue * 1000000) / 1000000;
|
||||
|
||||
const result = roundedValue / targetDivider;
|
||||
|
||||
return Number.isNaN(result) ? 0 : result;
|
||||
}
|
||||
|
||||
interface IUnit {
|
||||
value: number;
|
||||
sourceUnit?: string;
|
||||
targetUnit?: string;
|
||||
}
|
@ -1,4 +1,17 @@
|
||||
import { Form, InputNumber, InputNumberProps, Select, Typography } from 'antd';
|
||||
import {
|
||||
Form,
|
||||
InputNumber,
|
||||
InputNumberProps,
|
||||
Select,
|
||||
SelectProps,
|
||||
Space,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import {
|
||||
getCategoryByOptionId,
|
||||
getCategorySelectOptionByName,
|
||||
} from 'container/NewWidget/RightContainer/alertFomatCategories';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
AlertDef,
|
||||
@ -10,9 +23,6 @@ import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
import { FormContainer, InlineSelect, StepHeading } from './styles';
|
||||
|
||||
const { Option } = Select;
|
||||
const FormItem = Form.Item;
|
||||
|
||||
function RuleOptions({
|
||||
alertDef,
|
||||
setAlertDef,
|
||||
@ -20,6 +30,7 @@ function RuleOptions({
|
||||
}: RuleOptionsProps): JSX.Element {
|
||||
// init namespace for translations
|
||||
const { t } = useTranslation('alerts');
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
const handleMatchOptChange = (value: string | unknown): void => {
|
||||
const m = (value as string) || alertDef.condition?.matchType;
|
||||
@ -49,10 +60,10 @@ function RuleOptions({
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Option value="1">{t('option_above')}</Option>
|
||||
<Option value="2">{t('option_below')}</Option>
|
||||
<Option value="3">{t('option_equal')}</Option>
|
||||
<Option value="4">{t('option_notequal')}</Option>
|
||||
<Select.Option value="1">{t('option_above')}</Select.Option>
|
||||
<Select.Option value="2">{t('option_below')}</Select.Option>
|
||||
<Select.Option value="3">{t('option_equal')}</Select.Option>
|
||||
<Select.Option value="4">{t('option_notequal')}</Select.Option>
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
@ -63,10 +74,10 @@ function RuleOptions({
|
||||
value={alertDef.condition?.matchType}
|
||||
onChange={(value: string | unknown): void => handleMatchOptChange(value)}
|
||||
>
|
||||
<Option value="1">{t('option_atleastonce')}</Option>
|
||||
<Option value="2">{t('option_allthetimes')}</Option>
|
||||
<Option value="3">{t('option_onaverage')}</Option>
|
||||
<Option value="4">{t('option_intotal')}</Option>
|
||||
<Select.Option value="1">{t('option_atleastonce')}</Select.Option>
|
||||
<Select.Option value="2">{t('option_allthetimes')}</Select.Option>
|
||||
<Select.Option value="3">{t('option_onaverage')}</Select.Option>
|
||||
<Select.Option value="4">{t('option_intotal')}</Select.Option>
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
@ -77,7 +88,7 @@ function RuleOptions({
|
||||
value={alertDef.condition?.matchType}
|
||||
onChange={(value: string | unknown): void => handleMatchOptChange(value)}
|
||||
>
|
||||
<Option value="1">{t('option_atleastonce')}</Option>
|
||||
<Select.Option value="1">{t('option_atleastonce')}</Select.Option>
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
@ -94,31 +105,30 @@ function RuleOptions({
|
||||
});
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
<Option value="5m0s">{t('option_5min')}</Option>
|
||||
<Option value="10m0s">{t('option_10min')}</Option>
|
||||
<Option value="15m0s">{t('option_15min')}</Option>
|
||||
<Option value="1h0m0s">{t('option_60min')}</Option>
|
||||
<Option value="4h0m0s">{t('option_4hours')}</Option>
|
||||
<Option value="24h0m0s">{t('option_24hours')}</Option>
|
||||
<Select.Option value="5m0s">{t('option_5min')}</Select.Option>
|
||||
<Select.Option value="10m0s">{t('option_10min')}</Select.Option>
|
||||
<Select.Option value="15m0s">{t('option_15min')}</Select.Option>
|
||||
<Select.Option value="1h0m0s">{t('option_60min')}</Select.Option>
|
||||
<Select.Option value="4h0m0s">{t('option_4hours')}</Select.Option>
|
||||
<Select.Option value="24h0m0s">{t('option_24hours')}</Select.Option>
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
const renderThresholdRuleOpts = (): JSX.Element => (
|
||||
<FormItem>
|
||||
<Form.Item>
|
||||
<Typography.Text>
|
||||
{t('text_condition1')} {renderCompareOps()} {t('text_condition2')}{' '}
|
||||
{renderThresholdMatchOpts()} {t('text_condition3')} {renderEvalWindows()}
|
||||
</Typography.Text>
|
||||
</FormItem>
|
||||
</Form.Item>
|
||||
);
|
||||
const renderPromRuleOptions = (): JSX.Element => (
|
||||
<FormItem>
|
||||
<Form.Item>
|
||||
<Typography.Text>
|
||||
{t('text_condition1')} {renderCompareOps()} {t('text_condition2')}{' '}
|
||||
{renderPromMatchOpts()}
|
||||
</Typography.Text>
|
||||
</FormItem>
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
const onChange: InputNumberProps['onChange'] = (value): void => {
|
||||
@ -133,6 +143,22 @@ function RuleOptions({
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeAlertUnit: SelectProps['onChange'] = (value) => {
|
||||
setAlertDef({
|
||||
...alertDef,
|
||||
condition: {
|
||||
...alertDef.condition,
|
||||
targetUnit: value as string,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const selectedCategory = getCategoryByOptionId(currentQuery?.unit || '');
|
||||
|
||||
const categorySelectOptions = getCategorySelectOptionByName(
|
||||
selectedCategory?.name,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StepHeading>{t('alert_form_step2')}</StepHeading>
|
||||
@ -140,14 +166,29 @@ function RuleOptions({
|
||||
{queryCategory === EQueryType.PROM
|
||||
? renderPromRuleOptions()
|
||||
: renderThresholdRuleOpts()}
|
||||
<Form.Item name={['condition', 'target']}>
|
||||
<InputNumber
|
||||
addonBefore={t('field_threshold')}
|
||||
value={alertDef?.condition?.target}
|
||||
onChange={onChange}
|
||||
type="number"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Space align="start">
|
||||
<Form.Item noStyle name={['condition', 'target']}>
|
||||
<InputNumber
|
||||
addonBefore={t('field_threshold')}
|
||||
value={alertDef?.condition?.target}
|
||||
onChange={onChange}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
options={categorySelectOptions}
|
||||
placeholder={t('field_unit')}
|
||||
value={alertDef.condition.targetUnit}
|
||||
onChange={onChangeAlertUnit}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</FormContainer>
|
||||
</>
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
|
||||
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
|
||||
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||
@ -39,6 +40,7 @@ import {
|
||||
ButtonContainer,
|
||||
MainFormContainer,
|
||||
PanelContainer,
|
||||
StepContainer,
|
||||
StyledLeftContainer,
|
||||
} from './styles';
|
||||
import UserGuide from './UserGuide';
|
||||
@ -224,6 +226,7 @@ function FormAlertRules({
|
||||
chQueries: mapQueryDataToApi(currentQuery.clickhouse_sql, 'name').data,
|
||||
queryType: currentQuery.queryType,
|
||||
panelType: initQuery.panelType,
|
||||
unit: currentQuery.unit,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -360,9 +363,9 @@ function FormAlertRules({
|
||||
/>
|
||||
}
|
||||
name=""
|
||||
threshold={alertDef.condition?.target}
|
||||
query={stagedQuery}
|
||||
selectedInterval={toChartInterval(alertDef.evalWindow)}
|
||||
alertDef={alertDef}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -375,8 +378,8 @@ function FormAlertRules({
|
||||
/>
|
||||
}
|
||||
name="Chart Preview"
|
||||
threshold={alertDef.condition?.target}
|
||||
query={stagedQuery}
|
||||
alertDef={alertDef}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -389,8 +392,8 @@ function FormAlertRules({
|
||||
/>
|
||||
}
|
||||
name="Chart Preview"
|
||||
threshold={alertDef.condition?.target}
|
||||
query={stagedQuery}
|
||||
alertDef={alertDef}
|
||||
selectedInterval={toChartInterval(alertDef.evalWindow)}
|
||||
/>
|
||||
);
|
||||
@ -404,9 +407,21 @@ function FormAlertRules({
|
||||
currentQuery.queryType === EQueryType.QUERY_BUILDER &&
|
||||
alertType !== AlertTypes.METRICS_BASED_ALERT;
|
||||
|
||||
const onUnitChangeHandler = (): void => {
|
||||
// reset target unit
|
||||
setAlertDef((def) => ({
|
||||
...def,
|
||||
condition: {
|
||||
...def.condition,
|
||||
targetUnit: undefined,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{Element}
|
||||
|
||||
<PanelContainer>
|
||||
<StyledLeftContainer flex="5 1 600px" md={18}>
|
||||
<MainFormContainer
|
||||
@ -419,6 +434,11 @@ function FormAlertRules({
|
||||
{currentQuery.queryType === EQueryType.PROM && renderPromChartPreview()}
|
||||
{currentQuery.queryType === EQueryType.CLICKHOUSE &&
|
||||
renderChQueryChartPreview()}
|
||||
|
||||
<StepContainer>
|
||||
<BuilderUnitsFilter onChange={onUnitChangeHandler} />
|
||||
</StepContainer>
|
||||
|
||||
<QuerySection
|
||||
queryCategory={currentQuery.queryType}
|
||||
setQueryCategory={onQueryCategoryChange}
|
||||
|
@ -89,3 +89,7 @@ export const FormItemMedium = styled(Item)`
|
||||
export const ChannelSelectTip = styled(Typography.Text)`
|
||||
color: hsla(0, 0%, 100%, 0.3);
|
||||
`;
|
||||
|
||||
export const StepContainer = styled.div`
|
||||
margin-top: 2rem;
|
||||
`;
|
||||
|
@ -53,6 +53,7 @@ function WidgetGraphComponent({
|
||||
setLayout,
|
||||
onDragSelect,
|
||||
onClickHandler,
|
||||
threshold,
|
||||
headerMenuList,
|
||||
}: WidgetGraphComponentProps): JSX.Element {
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
@ -279,6 +280,7 @@ function WidgetGraphComponent({
|
||||
onClone={onCloneHandler}
|
||||
queryResponse={queryResponse}
|
||||
errorMessage={errorMessage}
|
||||
threshold={threshold}
|
||||
headerMenuList={headerMenuList}
|
||||
/>
|
||||
</div>
|
||||
|
@ -29,6 +29,7 @@ function GridCardGraph({
|
||||
onClickHandler,
|
||||
headerMenuList = [MenuItemKeys.View],
|
||||
isQueryEnabled,
|
||||
threshold,
|
||||
}: GridCardGraphProps): JSX.Element {
|
||||
const { isAddWidget } = useSelector<AppState, DashboardReducer>(
|
||||
(state) => state.dashboards,
|
||||
@ -70,11 +71,12 @@ function GridCardGraph({
|
||||
{
|
||||
queryKey: [
|
||||
`GetMetricsQueryRange-${widget?.timePreferance}-${globalSelectedInterval}-${widget?.id}`,
|
||||
widget,
|
||||
maxTime,
|
||||
minTime,
|
||||
globalSelectedInterval,
|
||||
variables,
|
||||
widget?.query,
|
||||
widget?.panelTypes,
|
||||
],
|
||||
keepPreviousData: true,
|
||||
enabled: isGraphVisible && !isEmptyWidget && isQueryEnabled && !isAddWidget,
|
||||
@ -101,11 +103,11 @@ function GridCardGraph({
|
||||
|
||||
const isEmptyLayout = widget?.id === 'empty' || isEmpty(widget);
|
||||
|
||||
if (queryResponse.isRefetching) {
|
||||
if (queryResponse.isRefetching || queryResponse.isLoading) {
|
||||
return <Spinner height="20vh" tip="Loading..." />;
|
||||
}
|
||||
|
||||
if (queryResponse.isError && !isEmptyLayout) {
|
||||
if ((queryResponse.isError && !isEmptyLayout) || !isQueryEnabled) {
|
||||
return (
|
||||
<span ref={graphRef}>
|
||||
{!isEmpty(widget) && prevChartDataSetRef && (
|
||||
@ -120,6 +122,7 @@ function GridCardGraph({
|
||||
yAxisUnit={yAxisUnit}
|
||||
layout={layout}
|
||||
setLayout={setLayout}
|
||||
threshold={threshold}
|
||||
headerMenuList={headerMenuList}
|
||||
/>
|
||||
)}
|
||||
@ -127,27 +130,24 @@ function GridCardGraph({
|
||||
);
|
||||
}
|
||||
|
||||
if (queryResponse.status === 'loading' || queryResponse.status === 'idle') {
|
||||
if (!isEmpty(widget) && prevChartDataSetRef?.labels) {
|
||||
return (
|
||||
<span ref={graphRef}>
|
||||
{!isEmpty(widget) && prevChartDataSetRef?.labels ? (
|
||||
<WidgetGraphComponent
|
||||
enableModel={false}
|
||||
enableWidgetHeader
|
||||
widget={widget}
|
||||
queryResponse={queryResponse}
|
||||
errorMessage={errorMessage}
|
||||
data={prevChartDataSetRef}
|
||||
name={name}
|
||||
yAxisUnit={yAxisUnit}
|
||||
layout={layout}
|
||||
setLayout={setLayout}
|
||||
headerMenuList={headerMenuList}
|
||||
onClickHandler={onClickHandler}
|
||||
/>
|
||||
) : (
|
||||
<Spinner height="20vh" tip="Loading..." />
|
||||
)}
|
||||
<WidgetGraphComponent
|
||||
enableModel={false}
|
||||
enableWidgetHeader
|
||||
widget={widget}
|
||||
queryResponse={queryResponse}
|
||||
errorMessage={errorMessage}
|
||||
data={prevChartDataSetRef}
|
||||
name={name}
|
||||
yAxisUnit={yAxisUnit}
|
||||
layout={layout}
|
||||
setLayout={setLayout}
|
||||
threshold={threshold}
|
||||
headerMenuList={headerMenuList}
|
||||
onClickHandler={onClickHandler}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -165,6 +165,7 @@ function GridCardGraph({
|
||||
name={name}
|
||||
yAxisUnit={yAxisUnit}
|
||||
onDragSelect={onDragSelect}
|
||||
threshold={threshold}
|
||||
headerMenuList={headerMenuList}
|
||||
onClickHandler={onClickHandler}
|
||||
/>
|
||||
@ -179,6 +180,7 @@ GridCardGraph.defaultProps = {
|
||||
onDragSelect: undefined,
|
||||
onClickHandler: undefined,
|
||||
isQueryEnabled: true,
|
||||
threshold: undefined,
|
||||
headerMenuList: [MenuItemKeys.View],
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ChartData } from 'chart.js';
|
||||
import { GraphOnClickHandler, ToggleGraphProps } from 'components/Graph/types';
|
||||
import { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
||||
import { Dispatch, MutableRefObject, ReactNode, SetStateAction } from 'react';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { DeleteWidgetProps } from 'store/actions/dashboard/deleteWidget';
|
||||
@ -39,6 +39,7 @@ export interface WidgetGraphComponentProps extends DispatchProps {
|
||||
setLayout?: Dispatch<SetStateAction<LayoutProps[]>>;
|
||||
onDragSelect?: (start: number, end: number) => void;
|
||||
onClickHandler?: GraphOnClickHandler;
|
||||
threshold?: ReactNode;
|
||||
headerMenuList: MenuItemKeys[];
|
||||
}
|
||||
|
||||
@ -50,6 +51,7 @@ export interface GridCardGraphProps {
|
||||
setLayout?: Dispatch<SetStateAction<LayoutProps[]>>;
|
||||
onDragSelect?: (start: number, end: number) => void;
|
||||
onClickHandler?: GraphOnClickHandler;
|
||||
threshold?: ReactNode;
|
||||
headerMenuList?: WidgetGraphComponentProps['headerMenuList'];
|
||||
isQueryEnabled: boolean;
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
|
||||
import {
|
||||
DisplayThresholdContainer,
|
||||
TypographHeading,
|
||||
Typography,
|
||||
} from './styles';
|
||||
import { DisplayThresholdProps } from './types';
|
||||
|
||||
function DisplayThreshold({ threshold }: DisplayThresholdProps): JSX.Element {
|
||||
return (
|
||||
<DisplayThresholdContainer>
|
||||
<TypographHeading>Threshold </TypographHeading>
|
||||
<Typography>{threshold || <InfoCircleOutlined />}</Typography>
|
||||
</DisplayThresholdContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default DisplayThreshold;
|
@ -12,7 +12,7 @@ import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import history from 'lib/history';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@ -32,12 +32,14 @@ import {
|
||||
ArrowContainer,
|
||||
HeaderContainer,
|
||||
HeaderContentContainer,
|
||||
ThesholdContainer,
|
||||
WidgetHeaderContainer,
|
||||
} from './styles';
|
||||
import { MenuItem } from './types';
|
||||
import { generateMenuList, isTWidgetOptions } from './utils';
|
||||
|
||||
interface IWidgetHeaderProps {
|
||||
title: string;
|
||||
title: ReactNode;
|
||||
widget: Widgets;
|
||||
onView: VoidFunction;
|
||||
onDelete?: VoidFunction;
|
||||
@ -47,6 +49,7 @@ interface IWidgetHeaderProps {
|
||||
SuccessResponse<MetricRangePayloadProps> | ErrorResponse
|
||||
>;
|
||||
errorMessage: string | undefined;
|
||||
threshold?: ReactNode;
|
||||
headerMenuList?: MenuItemKeys[];
|
||||
}
|
||||
|
||||
@ -59,6 +62,7 @@ function WidgetHeader({
|
||||
parentHover,
|
||||
queryResponse,
|
||||
errorMessage,
|
||||
threshold,
|
||||
headerMenuList,
|
||||
}: IWidgetHeaderProps): JSX.Element {
|
||||
const [localHover, setLocalHover] = useState(false);
|
||||
@ -171,7 +175,7 @@ function WidgetHeader({
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<WidgetHeaderContainer>
|
||||
<Dropdown
|
||||
destroyPopupOnHide
|
||||
open={isOpen}
|
||||
@ -196,6 +200,7 @@ function WidgetHeader({
|
||||
</HeaderContentContainer>
|
||||
</HeaderContainer>
|
||||
</Dropdown>
|
||||
<ThesholdContainer>{threshold}</ThesholdContainer>
|
||||
{queryResponse.isFetching && !queryResponse.isError && (
|
||||
<Spinner height="5vh" style={spinnerStyles} />
|
||||
)}
|
||||
@ -204,13 +209,14 @@ function WidgetHeader({
|
||||
<ExclamationCircleOutlined style={tooltipStyles} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</WidgetHeaderContainer>
|
||||
);
|
||||
}
|
||||
|
||||
WidgetHeader.defaultProps = {
|
||||
onDelete: undefined,
|
||||
onClone: undefined,
|
||||
threshold: undefined,
|
||||
headerMenuList: [MenuItemKeys.View],
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { grey } from '@ant-design/colors';
|
||||
import { Typography as TypographyComponent } from 'antd';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderContainer = styled.div<{ hover: boolean }>`
|
||||
@ -24,3 +26,34 @@ export const ArrowContainer = styled.span<{ hover: boolean }>`
|
||||
position: absolute;
|
||||
right: -1rem;
|
||||
`;
|
||||
|
||||
export const ThesholdContainer = styled.span`
|
||||
margin-top: -0.3rem;
|
||||
`;
|
||||
|
||||
export const DisplayThresholdContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
export const WidgetHeaderContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const Typography = styled(TypographyComponent)`
|
||||
&&& {
|
||||
color: ${themeColors.white};
|
||||
width: auto;
|
||||
margin-left: 0.2rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TypographHeading = styled(TypographyComponent)`
|
||||
&&& {
|
||||
color: ${grey[2]};
|
||||
}
|
||||
`;
|
||||
|
@ -10,3 +10,7 @@ export interface MenuItem {
|
||||
disabled: boolean;
|
||||
danger?: boolean;
|
||||
}
|
||||
|
||||
export interface DisplayThresholdProps {
|
||||
threshold: ReactNode;
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ export const UpdateDashboard = async (
|
||||
query: widgetData?.query || initialQueriesMap.metrics,
|
||||
timePreferance: widgetData?.timePreferance || 'GLOBAL_TIME',
|
||||
title: widgetData ? copyTitle : '',
|
||||
yAxisUnit: widgetData?.yAxisUnit,
|
||||
},
|
||||
],
|
||||
layout,
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
} from 'components/Graph/types';
|
||||
import { GridTableComponentProps } from 'container/GridTableComponent/types';
|
||||
import { GridValueComponentProps } from 'container/GridValueComponent/types';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryDataV3 } from 'types/api/widgets/getQuery';
|
||||
|
||||
@ -14,7 +15,7 @@ import { PANEL_TYPES } from '../../constants/queryBuilder';
|
||||
export type GridPanelSwitchProps = {
|
||||
panelType: PANEL_TYPES;
|
||||
data: ChartData;
|
||||
title?: string;
|
||||
title?: Widgets['title'];
|
||||
opacity?: string;
|
||||
isStacked?: boolean;
|
||||
onClickHandler?: GraphOnClickHandler;
|
||||
|
12
frontend/src/container/GridPanelSwitch/utils.ts
Normal file
12
frontend/src/container/GridPanelSwitch/utils.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
export const generateGridTitle = (title: ReactNode): string => {
|
||||
if (React.isValidElement(title)) {
|
||||
return Array.isArray(title.props.children)
|
||||
? title.props.children
|
||||
.map((child: ReactNode) => (typeof child === 'string' ? child : ''))
|
||||
.join(' ')
|
||||
: title.props.children;
|
||||
}
|
||||
return title?.toString() || '';
|
||||
};
|
@ -1,7 +1,8 @@
|
||||
import { Typography } from 'antd';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
import ValueGraph from 'components/ValueGraph';
|
||||
import { memo } from 'react';
|
||||
import { generateGridTitle } from 'container/GridPanelSwitch/utils';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { TitleContainer, ValueContainer } from './styles';
|
||||
@ -15,6 +16,7 @@ function GridValueComponent({
|
||||
const value = (((data.datasets[0] || []).data || [])[0] || 0) as number;
|
||||
|
||||
const location = useLocation();
|
||||
const gridTitle = useMemo(() => generateGridTitle(title), [title]);
|
||||
|
||||
const isDashboardPage = location.pathname.split('/').length === 3;
|
||||
|
||||
@ -29,7 +31,7 @@ function GridValueComponent({
|
||||
return (
|
||||
<>
|
||||
<TitleContainer isDashboardPage={isDashboardPage}>
|
||||
<Typography>{title}</Typography>
|
||||
<Typography>{gridTitle}</Typography>
|
||||
</TitleContainer>
|
||||
<ValueContainer isDashboardPage={isDashboardPage}>
|
||||
<ValueGraph
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { ChartData } from 'chart.js';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export type GridValueComponentProps = {
|
||||
data: ChartData;
|
||||
title?: string;
|
||||
title?: ReactNode;
|
||||
yAxisUnit?: string;
|
||||
};
|
||||
|
@ -18,7 +18,13 @@ import {
|
||||
QUERYNAME_AND_EXPRESSION,
|
||||
WidgetKeys,
|
||||
} from '../constant';
|
||||
import { LatencyProps, OperationPerSecProps } from '../Tabs/types';
|
||||
import {
|
||||
ApDexMetricsQueryBuilderQueriesProps,
|
||||
ApDexProps,
|
||||
LatencyProps,
|
||||
OperationPerSecProps,
|
||||
} from '../Tabs/types';
|
||||
import { convertMilSecToNanoSec, getNearestHighestBucketValue } from '../utils';
|
||||
import {
|
||||
getQueryBuilderQueries,
|
||||
getQueryBuilderQuerieswithFormula,
|
||||
@ -85,6 +91,365 @@ export const latency = ({
|
||||
});
|
||||
};
|
||||
|
||||
export const apDexTracesQueryBuilderQueries = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperationsRoute,
|
||||
threashold,
|
||||
}: ApDexProps): QueryBuilderData => {
|
||||
const autoCompleteDataA: BaseAutocompleteData = {
|
||||
dataType: DataType.FLOAT64,
|
||||
isColumn: true,
|
||||
key: '',
|
||||
type: null,
|
||||
};
|
||||
|
||||
const autoCompleteDataB: BaseAutocompleteData = {
|
||||
dataType: DataType.FLOAT64,
|
||||
isColumn: true,
|
||||
key: '',
|
||||
type: null,
|
||||
};
|
||||
|
||||
const autoCompleteDataC: BaseAutocompleteData = {
|
||||
dataType: DataType.FLOAT64,
|
||||
isColumn: true,
|
||||
key: '',
|
||||
type: null,
|
||||
};
|
||||
|
||||
const filterItemA: TagFilterItem[] = [
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.ServiceName,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: true,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: servicename,
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.Name,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: true,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
value: [...topLevelOperationsRoute],
|
||||
},
|
||||
...tagFilterItems,
|
||||
];
|
||||
|
||||
const filterItemB: TagFilterItem[] = [
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.HasError,
|
||||
dataType: DataType.BOOL,
|
||||
isColumn: true,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.DurationNano,
|
||||
dataType: DataType.FLOAT64,
|
||||
isColumn: true,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['<='],
|
||||
value: convertMilSecToNanoSec(threashold),
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.ServiceName,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: true,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: servicename,
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.Name,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: true,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
value: [...topLevelOperationsRoute],
|
||||
},
|
||||
];
|
||||
|
||||
const filterItemC: TagFilterItem[] = [
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.DurationNano,
|
||||
dataType: DataType.FLOAT64,
|
||||
isColumn: true,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['<='],
|
||||
value: convertMilSecToNanoSec(threashold * 4),
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.HasError,
|
||||
dataType: DataType.BOOL,
|
||||
isColumn: true,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.ServiceName,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: true,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: servicename,
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.Name,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: true,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
value: [...topLevelOperationsRoute],
|
||||
},
|
||||
];
|
||||
|
||||
const autocompleteData = [
|
||||
autoCompleteDataA,
|
||||
autoCompleteDataB,
|
||||
autoCompleteDataC,
|
||||
];
|
||||
const additionalItems = [filterItemA, filterItemB, filterItemC];
|
||||
const legends = [GraphTitle.APDEX];
|
||||
const disabled = Array(3).fill(true);
|
||||
const expressions = [FORMULA.APDEX_TRACES];
|
||||
const legendFormulas = [GraphTitle.APDEX];
|
||||
const aggregateOperators = [
|
||||
MetricAggregateOperator.COUNT,
|
||||
MetricAggregateOperator.COUNT,
|
||||
MetricAggregateOperator.COUNT,
|
||||
];
|
||||
const dataSource = DataSource.TRACES;
|
||||
|
||||
return getQueryBuilderQuerieswithFormula({
|
||||
autocompleteData,
|
||||
additionalItems,
|
||||
legends,
|
||||
disabled,
|
||||
expressions,
|
||||
legendFormulas,
|
||||
aggregateOperators,
|
||||
dataSource,
|
||||
});
|
||||
};
|
||||
|
||||
export const apDexMetricsQueryBuilderQueries = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperationsRoute,
|
||||
threashold,
|
||||
delta,
|
||||
metricsBuckets,
|
||||
}: ApDexMetricsQueryBuilderQueriesProps): QueryBuilderData => {
|
||||
const autoCompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
dataType: DataType.FLOAT64,
|
||||
isColumn: true,
|
||||
type: null,
|
||||
};
|
||||
|
||||
const autoCompleteDataB: BaseAutocompleteData = {
|
||||
key: WidgetKeys.Signoz_latency_bucket,
|
||||
dataType: DataType.FLOAT64,
|
||||
isColumn: true,
|
||||
type: null,
|
||||
};
|
||||
|
||||
const autoCompleteDataC: BaseAutocompleteData = {
|
||||
key: WidgetKeys.Signoz_latency_bucket,
|
||||
dataType: DataType.FLOAT64,
|
||||
isColumn: true,
|
||||
type: null,
|
||||
};
|
||||
|
||||
const filterItemA: TagFilterItem[] = [
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.Service_name,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: false,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: servicename,
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.Operation,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: false,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
value: [...topLevelOperationsRoute],
|
||||
},
|
||||
...tagFilterItems,
|
||||
];
|
||||
|
||||
const filterItemB: TagFilterItem[] = [
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: false,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: 'STATUS_CODE_UNSET',
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.Le,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: false,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: getNearestHighestBucketValue(threashold * 1000, metricsBuckets),
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.Service_name,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: false,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: servicename,
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.Operation,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: false,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
value: [...topLevelOperationsRoute],
|
||||
},
|
||||
...tagFilterItems,
|
||||
];
|
||||
|
||||
const filterItemC: TagFilterItem[] = [
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.Le,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: false,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: getNearestHighestBucketValue(threashold * 1000 * 4, metricsBuckets),
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: false,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: 'STATUS_CODE_UNSET',
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.Service_name,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: false,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS['='],
|
||||
value: servicename,
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.Operation,
|
||||
dataType: DataType.STRING,
|
||||
isColumn: false,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
value: [...topLevelOperationsRoute],
|
||||
},
|
||||
...tagFilterItems,
|
||||
];
|
||||
|
||||
const autocompleteData = [
|
||||
autoCompleteDataA,
|
||||
autoCompleteDataB,
|
||||
autoCompleteDataC,
|
||||
];
|
||||
|
||||
const additionalItems = [filterItemA, filterItemB, filterItemC];
|
||||
const legends = [GraphTitle.APDEX];
|
||||
const disabled = Array(3).fill(true);
|
||||
const expressions = delta
|
||||
? [FORMULA.APDEX_DELTA_SPAN_METRICS]
|
||||
: [FORMULA.APDEX_CUMULATIVE_SPAN_METRICS];
|
||||
const legendFormulas = [GraphTitle.APDEX];
|
||||
const aggregateOperators = [
|
||||
MetricAggregateOperator.SUM_RATE,
|
||||
MetricAggregateOperator.SUM_RATE,
|
||||
MetricAggregateOperator.SUM_RATE,
|
||||
];
|
||||
const dataSource = DataSource.METRICS;
|
||||
|
||||
return getQueryBuilderQuerieswithFormula({
|
||||
autocompleteData,
|
||||
additionalItems,
|
||||
legends,
|
||||
disabled,
|
||||
expressions,
|
||||
legendFormulas,
|
||||
aggregateOperators,
|
||||
dataSource,
|
||||
});
|
||||
};
|
||||
|
||||
export const operationPerSec = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
|
@ -32,7 +32,14 @@ import {
|
||||
errorPercentage,
|
||||
operationPerSec,
|
||||
} from '../MetricsPageQueries/OverviewQueries';
|
||||
import { Card, Col, Row } from '../styles';
|
||||
import {
|
||||
Card,
|
||||
Col,
|
||||
ColApDexContainer,
|
||||
ColErrorContainer,
|
||||
Row,
|
||||
} from '../styles';
|
||||
import ApDex from './Overview/ApDex';
|
||||
import ServiceOverview from './Overview/ServiceOverview';
|
||||
import TopLevelOperation from './Overview/TopLevelOperations';
|
||||
import TopOperation from './Overview/TopOperation';
|
||||
@ -160,7 +167,7 @@ function Application(): JSX.Element {
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const onErrorTrackHandler = (timestamp: number): void => {
|
||||
const onErrorTrackHandler = (timestamp: number): (() => void) => (): void => {
|
||||
const currentTime = timestamp;
|
||||
const tPlusOne = timestamp + 60 * 1000;
|
||||
|
||||
@ -190,6 +197,7 @@ function Application(): JSX.Element {
|
||||
selectedTimeStamp={selectedTimeStamp}
|
||||
selectedTraceTags={selectedTraceTags}
|
||||
topLevelOperationsRoute={topLevelOperationsRoute}
|
||||
topLevelOperationsLoading={topLevelOperationsLoading}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
@ -221,28 +229,48 @@ function Application(): JSX.Element {
|
||||
</Row>
|
||||
<Row gutter={24}>
|
||||
<Col span={12}>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="Error_button"
|
||||
onClick={(): void => {
|
||||
onErrorTrackHandler(selectedTimeStamp);
|
||||
}}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
<ColApDexContainer>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="ApDex_button"
|
||||
onClick={onViewTracePopupClick({
|
||||
servicename,
|
||||
selectedTraceTags,
|
||||
timestamp: selectedTimeStamp,
|
||||
})}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
<ApDex
|
||||
handleGraphClick={handleGraphClick}
|
||||
onDragSelect={onDragSelect}
|
||||
topLevelOperationsRoute={topLevelOperationsRoute}
|
||||
tagFilterItems={tagFilterItems}
|
||||
/>
|
||||
</ColApDexContainer>
|
||||
<ColErrorContainer>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="Error_button"
|
||||
onClick={onErrorTrackHandler(selectedTimeStamp)}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
|
||||
<TopLevelOperation
|
||||
handleGraphClick={handleGraphClick}
|
||||
onDragSelect={onDragSelect}
|
||||
topLevelOperationsError={topLevelOperationsError}
|
||||
topLevelOperationsLoading={topLevelOperationsLoading}
|
||||
topLevelOperationsIsError={topLevelOperationsIsError}
|
||||
name="error_percentage_%"
|
||||
widget={errorPercentageWidget}
|
||||
yAxisUnit="%"
|
||||
opName="Error"
|
||||
/>
|
||||
<TopLevelOperation
|
||||
handleGraphClick={handleGraphClick}
|
||||
onDragSelect={onDragSelect}
|
||||
topLevelOperationsError={topLevelOperationsError}
|
||||
topLevelOperationsLoading={topLevelOperationsLoading}
|
||||
topLevelOperationsIsError={topLevelOperationsIsError}
|
||||
name="error_percentage_%"
|
||||
widget={errorPercentageWidget}
|
||||
yAxisUnit="%"
|
||||
opName="Error"
|
||||
/>
|
||||
</ColErrorContainer>
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
|
@ -0,0 +1,102 @@
|
||||
import { Space, Typography } from 'antd';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import {
|
||||
apDexToolTipText,
|
||||
apDexToolTipUrl,
|
||||
apDexToolTipUrlText,
|
||||
} from 'constants/apDex';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import Graph from 'container/GridGraphLayout/Graph';
|
||||
import DisplayThreshold from 'container/GridGraphLayout/WidgetHeader/DisplayThreshold';
|
||||
import { GraphTitle } from 'container/MetricsApplication/constant';
|
||||
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||
import { apDexMetricsQueryBuilderQueries } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { IServiceName } from '../../types';
|
||||
import { ApDexMetricsProps } from './types';
|
||||
|
||||
function ApDexMetrics({
|
||||
delta,
|
||||
metricsBuckets,
|
||||
thresholdValue,
|
||||
onDragSelect,
|
||||
tagFilterItems,
|
||||
topLevelOperationsRoute,
|
||||
handleGraphClick,
|
||||
}: ApDexMetricsProps): JSX.Element {
|
||||
const { servicename } = useParams<IServiceName>();
|
||||
|
||||
const apDexMetricsWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
promql: [],
|
||||
builder: apDexMetricsQueryBuilderQueries({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperationsRoute,
|
||||
threashold: thresholdValue || 0,
|
||||
delta: delta || false,
|
||||
metricsBuckets: metricsBuckets || [],
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
title: (
|
||||
<Space>
|
||||
<Typography>{GraphTitle.APDEX}</Typography>
|
||||
<TextToolTip
|
||||
text={apDexToolTipText}
|
||||
url={apDexToolTipUrl}
|
||||
useFilledIcon={false}
|
||||
urlText={apDexToolTipUrlText}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
}),
|
||||
[
|
||||
delta,
|
||||
metricsBuckets,
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
thresholdValue,
|
||||
topLevelOperationsRoute,
|
||||
],
|
||||
);
|
||||
|
||||
const threshold: ReactNode = useMemo(() => {
|
||||
if (thresholdValue) return <DisplayThreshold threshold={thresholdValue} />;
|
||||
return null;
|
||||
}, [thresholdValue]);
|
||||
|
||||
const isQueryEnabled =
|
||||
topLevelOperationsRoute.length > 0 &&
|
||||
metricsBuckets &&
|
||||
metricsBuckets?.length > 0 &&
|
||||
delta !== undefined;
|
||||
|
||||
return (
|
||||
<Graph
|
||||
name="apdex"
|
||||
widget={apDexMetricsWidget}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={handleGraphClick('ApDex')}
|
||||
yAxisUnit=""
|
||||
threshold={threshold}
|
||||
isQueryEnabled={isQueryEnabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
ApDexMetrics.defaultProps = {
|
||||
delta: undefined,
|
||||
le: undefined,
|
||||
};
|
||||
|
||||
export default ApDexMetrics;
|
@ -0,0 +1,36 @@
|
||||
import Spinner from 'components/Spinner';
|
||||
import { useGetMetricMeta } from 'hooks/apDex/useGetMetricMeta';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
|
||||
import ApDexMetrics from './ApDexMetrics';
|
||||
import { metricMeta } from './constants';
|
||||
import { ApDexDataSwitcherProps } from './types';
|
||||
|
||||
function ApDexMetricsApplication({
|
||||
handleGraphClick,
|
||||
onDragSelect,
|
||||
tagFilterItems,
|
||||
topLevelOperationsRoute,
|
||||
thresholdValue,
|
||||
}: ApDexDataSwitcherProps): JSX.Element {
|
||||
const { data, isLoading, error } = useGetMetricMeta(metricMeta);
|
||||
useErrorNotification(error);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner height="40vh" tip="Loading..." />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ApDexMetrics
|
||||
handleGraphClick={handleGraphClick}
|
||||
delta={data?.data.delta}
|
||||
metricsBuckets={data?.data.le}
|
||||
onDragSelect={onDragSelect}
|
||||
topLevelOperationsRoute={topLevelOperationsRoute}
|
||||
tagFilterItems={tagFilterItems}
|
||||
thresholdValue={thresholdValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ApDexMetricsApplication;
|
@ -0,0 +1,62 @@
|
||||
// This component is not been used in the application as we support only metrics for ApDex as of now.
|
||||
// This component is been kept for future reference.
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import Graph from 'container/GridGraphLayout/Graph';
|
||||
import { GraphTitle } from 'container/MetricsApplication/constant';
|
||||
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||
import { apDexTracesQueryBuilderQueries } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { IServiceName } from '../../types';
|
||||
import { ApDexDataSwitcherProps } from './types';
|
||||
|
||||
function ApDexTraces({
|
||||
handleGraphClick,
|
||||
onDragSelect,
|
||||
topLevelOperationsRoute,
|
||||
tagFilterItems,
|
||||
thresholdValue,
|
||||
}: ApDexDataSwitcherProps): JSX.Element {
|
||||
const { servicename } = useParams<IServiceName>();
|
||||
|
||||
const apDexTracesWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
promql: [],
|
||||
builder: apDexTracesQueryBuilderQueries({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperationsRoute,
|
||||
threashold: thresholdValue || 0,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
title: GraphTitle.APDEX,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
}),
|
||||
[servicename, tagFilterItems, thresholdValue, topLevelOperationsRoute],
|
||||
);
|
||||
|
||||
const isQueryEnabled =
|
||||
topLevelOperationsRoute.length > 0 && thresholdValue !== undefined;
|
||||
|
||||
return (
|
||||
<Graph
|
||||
name="apdex"
|
||||
widget={apDexTracesWidget}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={handleGraphClick('ApDex')}
|
||||
yAxisUnit=""
|
||||
threshold={thresholdValue}
|
||||
isQueryEnabled={isQueryEnabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ApDexTraces;
|
@ -0,0 +1 @@
|
||||
export const metricMeta = 'signoz_latency_bucket';
|
@ -0,0 +1,47 @@
|
||||
import Spinner from 'components/Spinner';
|
||||
import { Card, GraphContainer } from 'container/MetricsApplication/styles';
|
||||
import { useGetApDexSettings } from 'hooks/apDex/useGetApDexSettings';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
import { memo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { IServiceName } from '../../types';
|
||||
import ApDexMetricsApplication from './ApDexMetricsApplication';
|
||||
import { ApDexApplicationProps } from './types';
|
||||
|
||||
function ApDexApplication({
|
||||
handleGraphClick,
|
||||
onDragSelect,
|
||||
topLevelOperationsRoute,
|
||||
tagFilterItems,
|
||||
}: ApDexApplicationProps): JSX.Element {
|
||||
const { servicename } = useParams<IServiceName>();
|
||||
const { data, isLoading, error, isRefetching } = useGetApDexSettings(
|
||||
servicename,
|
||||
);
|
||||
useErrorNotification(error);
|
||||
|
||||
if (isLoading || isRefetching) {
|
||||
return (
|
||||
<Card>
|
||||
<Spinner height="40vh" tip="Loading..." />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<GraphContainer>
|
||||
<ApDexMetricsApplication
|
||||
handleGraphClick={handleGraphClick}
|
||||
onDragSelect={onDragSelect}
|
||||
topLevelOperationsRoute={topLevelOperationsRoute}
|
||||
tagFilterItems={tagFilterItems}
|
||||
thresholdValue={data?.data[0].threshold}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(ApDexApplication);
|
@ -0,0 +1,19 @@
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { ClickHandlerType } from '../../Overview';
|
||||
|
||||
export interface ApDexApplicationProps {
|
||||
handleGraphClick: (type: string) => ClickHandlerType;
|
||||
onDragSelect: (start: number, end: number) => void;
|
||||
topLevelOperationsRoute: string[];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
}
|
||||
|
||||
export interface ApDexDataSwitcherProps extends ApDexApplicationProps {
|
||||
thresholdValue?: number;
|
||||
}
|
||||
|
||||
export interface ApDexMetricsProps extends ApDexDataSwitcherProps {
|
||||
delta?: boolean;
|
||||
metricsBuckets?: number[];
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import Spinner from 'components/Spinner';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import Graph from 'container/GridGraphLayout/Graph/';
|
||||
@ -24,6 +25,7 @@ function ServiceOverview({
|
||||
selectedTraceTags,
|
||||
selectedTimeStamp,
|
||||
topLevelOperationsRoute,
|
||||
topLevelOperationsLoading,
|
||||
}: ServiceOverviewProps): JSX.Element {
|
||||
const { servicename } = useParams<IServiceName>();
|
||||
|
||||
@ -63,6 +65,14 @@ function ServiceOverview({
|
||||
|
||||
const isQueryEnabled = topLevelOperationsRoute.length > 0;
|
||||
|
||||
if (topLevelOperationsLoading) {
|
||||
return (
|
||||
<Card>
|
||||
<Spinner height="40vh" tip="Loading..." />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
@ -99,6 +109,7 @@ interface ServiceOverviewProps {
|
||||
onDragSelect: (start: number, end: number) => void;
|
||||
handleGraphClick: (type: string) => ClickHandlerType;
|
||||
topLevelOperationsRoute: string[];
|
||||
topLevelOperationsLoading: boolean;
|
||||
}
|
||||
|
||||
export default ServiceOverview;
|
||||
|
@ -56,7 +56,19 @@ export interface LatencyProps {
|
||||
topLevelOperationsRoute: string[];
|
||||
}
|
||||
|
||||
export interface ApDexProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
topLevelOperationsRoute: string[];
|
||||
threashold: number;
|
||||
}
|
||||
|
||||
export interface TableRendererProps {
|
||||
columnName: string;
|
||||
renderFunction: (record: RowData) => ReactNode;
|
||||
}
|
||||
|
||||
export interface ApDexMetricsQueryBuilderQueriesProps extends ApDexProps {
|
||||
delta: boolean;
|
||||
metricsBuckets: number[];
|
||||
}
|
||||
|
@ -14,9 +14,13 @@ export const OPERATION_LEGENDS = ['Operations'];
|
||||
export enum FORMULA {
|
||||
ERROR_PERCENTAGE = 'A*100/B',
|
||||
DATABASE_CALLS_AVG_DURATION = 'A/B',
|
||||
APDEX_TRACES = '((B + C)/2)/A',
|
||||
APDEX_DELTA_SPAN_METRICS = '(B + C/2)/A',
|
||||
APDEX_CUMULATIVE_SPAN_METRICS = '((B + C)/2)/A',
|
||||
}
|
||||
|
||||
export enum GraphTitle {
|
||||
APDEX = 'Apdex',
|
||||
LATENCY = 'Latency',
|
||||
RATE_PER_OPS = 'Rate (ops/s)',
|
||||
ERROR_PERCENTAGE = 'Error Percentage',
|
||||
@ -41,6 +45,7 @@ export enum DataType {
|
||||
STRING = 'string',
|
||||
FLOAT64 = 'float64',
|
||||
INT64 = 'int64',
|
||||
BOOL = 'bool',
|
||||
}
|
||||
|
||||
export enum MetricsType {
|
||||
@ -49,7 +54,9 @@ export enum MetricsType {
|
||||
}
|
||||
|
||||
export enum WidgetKeys {
|
||||
Le = 'le',
|
||||
Name = 'name',
|
||||
HasError = 'hasError',
|
||||
Address = 'address',
|
||||
DurationNano = 'durationNano',
|
||||
StatusCode = 'status_code',
|
||||
|
@ -29,6 +29,14 @@ export const Col = styled(ColComponent)`
|
||||
}
|
||||
`;
|
||||
|
||||
export const ColApDexContainer = styled(ColComponent)`
|
||||
padding: 0 !important;
|
||||
`;
|
||||
|
||||
export const ColErrorContainer = styled(ColComponent)`
|
||||
padding: 2rem 0 !important;
|
||||
`;
|
||||
|
||||
export const GraphContainer = styled.div`
|
||||
height: 40vh;
|
||||
`;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
@ -5,7 +6,7 @@ import { IServiceName } from './Tabs/types';
|
||||
|
||||
export interface GetWidgetQueryBuilderProps {
|
||||
query: Widgets['query'];
|
||||
title?: string;
|
||||
title?: ReactNode;
|
||||
panelTypes: Widgets['panelTypes'];
|
||||
}
|
||||
|
||||
|
@ -24,3 +24,14 @@ export const navigateToTrace = ({
|
||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1`,
|
||||
);
|
||||
};
|
||||
|
||||
export const getNearestHighestBucketValue = (
|
||||
value: number,
|
||||
buckets: number[],
|
||||
): string => {
|
||||
const nearestBucket = buckets.find((bucket) => bucket >= value);
|
||||
return nearestBucket?.toString() || '+Inf';
|
||||
};
|
||||
|
||||
export const convertMilSecToNanoSec = (value: number): number =>
|
||||
value * 1000000000;
|
||||
|
@ -0,0 +1,121 @@
|
||||
import { DefaultOptionType } from 'antd/es/select';
|
||||
|
||||
import {
|
||||
BooleanFormats,
|
||||
Category,
|
||||
CategoryNames,
|
||||
DataFormats,
|
||||
DataRateFormats,
|
||||
MiscellaneousFormats,
|
||||
ThroughputFormats,
|
||||
TimeFormats,
|
||||
} from './types';
|
||||
|
||||
export const alertsCategory = [
|
||||
{
|
||||
name: CategoryNames.Time,
|
||||
formats: [
|
||||
{ name: 'nanoseconds (ns)', id: TimeFormats.Nanoseconds },
|
||||
{ name: 'microseconds (µs)', id: TimeFormats.Microseconds },
|
||||
{ name: 'milliseconds (ms)', id: TimeFormats.Milliseconds },
|
||||
{ name: 'seconds (s)', id: TimeFormats.Seconds },
|
||||
{ name: 'minutes (m)', id: TimeFormats.Minutes },
|
||||
{ name: 'hours (h)', id: TimeFormats.Hours },
|
||||
{ name: 'days (d)', id: TimeFormats.Days },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Data,
|
||||
formats: [
|
||||
{ name: 'bytes(IEC)', id: DataFormats.BytesIEC },
|
||||
{ name: 'bytes(SI)', id: DataFormats.BytesSI },
|
||||
{ name: 'bits(IEC)', id: DataFormats.BitsIEC },
|
||||
{ name: 'bits(SI)', id: DataFormats.BitsSI },
|
||||
{ name: 'kibibytes', id: DataFormats.KibiBytes },
|
||||
{ name: 'kilobytes', id: DataFormats.KiloBytes },
|
||||
{ name: 'mebibytes', id: DataFormats.MebiBytes },
|
||||
{ name: 'megabytes', id: DataFormats.MegaBytes },
|
||||
{ name: 'gibibytes', id: DataFormats.GibiBytes },
|
||||
{ name: 'gigabytes', id: DataFormats.GigaBytes },
|
||||
{ name: 'tebibytes', id: DataFormats.TebiBytes },
|
||||
{ name: 'terabytes', id: DataFormats.TeraBytes },
|
||||
{ name: 'pebibytes', id: DataFormats.PebiBytes },
|
||||
{ name: 'petabytes', id: DataFormats.PetaBytes },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.DataRate,
|
||||
formats: [
|
||||
{ name: 'bytes/sec(IEC)', id: DataRateFormats.BytesPerSecIEC },
|
||||
{ name: 'bytes/sec(SI)', id: DataRateFormats.BytesPerSecSI },
|
||||
{ name: 'bits/sec(IEC)', id: DataRateFormats.BitsPerSecIEC },
|
||||
{ name: 'bits/sec(SI)', id: DataRateFormats.BitsPerSecSI },
|
||||
{ name: 'kibibytes/sec', id: DataRateFormats.KibiBytesPerSec },
|
||||
{ name: 'kibibits/sec', id: DataRateFormats.KibiBitsPerSec },
|
||||
{ name: 'kilobytes/sec', id: DataRateFormats.KiloBytesPerSec },
|
||||
{ name: 'kilobits/sec', id: DataRateFormats.KiloBitsPerSec },
|
||||
{ name: 'mebibytes/sec', id: DataRateFormats.MebiBytesPerSec },
|
||||
{ name: 'mebibits/sec', id: DataRateFormats.MebiBitsPerSec },
|
||||
{ name: 'megabytes/sec', id: DataRateFormats.MegaBytesPerSec },
|
||||
{ name: 'megabits/sec', id: DataRateFormats.MegaBitsPerSec },
|
||||
{ name: 'gibibytes/sec', id: DataRateFormats.GibiBytesPerSec },
|
||||
{ name: 'gibibits/sec', id: DataRateFormats.GibiBitsPerSec },
|
||||
{ name: 'gigabytes/sec', id: DataRateFormats.GigaBytesPerSec },
|
||||
{ name: 'gigabits/sec', id: DataRateFormats.GigaBitsPerSec },
|
||||
{ name: 'tebibytes/sec', id: DataRateFormats.TebiBytesPerSec },
|
||||
{ name: 'tebibits/sec', id: DataRateFormats.TebiBitsPerSec },
|
||||
{ name: 'terabytes/sec', id: DataRateFormats.TeraBytesPerSec },
|
||||
{ name: 'terabits/sec', id: DataRateFormats.TeraBitsPerSec },
|
||||
{ name: 'pebibytes/sec', id: DataRateFormats.PebiBytesPerSec },
|
||||
{ name: 'pebibits/sec', id: DataRateFormats.PebiBitsPerSec },
|
||||
{ name: 'petabytes/sec', id: DataRateFormats.PetaBytesPerSec },
|
||||
{ name: 'petabits/sec', id: DataRateFormats.PetaBitsPerSec },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Miscellaneous,
|
||||
formats: [
|
||||
{ name: 'Percent (0.0-1.0)', id: MiscellaneousFormats.PercentUnit },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Boolean,
|
||||
formats: [
|
||||
{ name: 'True / False', id: BooleanFormats.TRUE_FALSE },
|
||||
{ name: 'Yes / No', id: BooleanFormats.YES_NO },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Throughput,
|
||||
formats: [
|
||||
{ name: 'counts/sec (cps)', id: ThroughputFormats.CountsPerSec },
|
||||
{ name: 'ops/sec (ops)', id: ThroughputFormats.OpsPerSec },
|
||||
{ name: 'requests/sec (reqps)', id: ThroughputFormats.RequestsPerSec },
|
||||
{ name: 'reads/sec (rps)', id: ThroughputFormats.ReadsPerSec },
|
||||
{ name: 'writes/sec (wps)', id: ThroughputFormats.WritesPerSec },
|
||||
{ name: 'I/O operations/sec (iops)', id: ThroughputFormats.IOOpsPerSec },
|
||||
{ name: 'counts/min (cpm)', id: ThroughputFormats.CountsPerMin },
|
||||
{ name: 'ops/min (opm)', id: ThroughputFormats.OpsPerMin },
|
||||
{ name: 'reads/min (rpm)', id: ThroughputFormats.ReadsPerMin },
|
||||
{ name: 'writes/min (wpm)', id: ThroughputFormats.WritesPerMin },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const getCategorySelectOptionByName = (
|
||||
name?: CategoryNames | string,
|
||||
): DefaultOptionType[] =>
|
||||
alertsCategory
|
||||
.find((category) => category.name === name)
|
||||
?.formats.map((format) => ({
|
||||
label: format.name,
|
||||
value: format.id,
|
||||
})) || [];
|
||||
|
||||
export const getCategoryByOptionId = (id: string): Category | undefined =>
|
||||
alertsCategory.find((category) =>
|
||||
category.formats.some((format) => format.id === id),
|
||||
);
|
||||
|
||||
export const isCategoryName = (name: string): name is CategoryNames =>
|
||||
alertsCategory.some((category) => category.name === name);
|
@ -1,383 +1,436 @@
|
||||
import { flattenDeep } from 'lodash-es';
|
||||
|
||||
export const dataTypeCategories = [
|
||||
{
|
||||
name: 'Time',
|
||||
formats: [
|
||||
{ name: 'Hertz (1/s)', id: 'hertz' },
|
||||
{ name: 'nanoseconds (ns)', id: 'ns' },
|
||||
{ name: 'microseconds (µs)', id: 'µs' },
|
||||
{ name: 'milliseconds (ms)', id: 'ms' },
|
||||
{ name: 'seconds (s)', id: 's' },
|
||||
{ name: 'minutes (m)', id: 'm' },
|
||||
{ name: 'hours (h)', id: 'h' },
|
||||
{ name: 'days (d)', id: 'd' },
|
||||
{ name: 'duration (ms)', id: 'dtdurationms' },
|
||||
{ name: 'duration (s)', id: 'dtdurations' },
|
||||
{ name: 'duration (hh:mm:ss)', id: 'dthms' },
|
||||
{ name: 'duration (d hh:mm:ss)', id: 'dtdhms' },
|
||||
{ name: 'Timeticks (s/100)', id: 'timeticks' },
|
||||
{ name: 'clock (ms)', id: 'clockms' },
|
||||
{ name: 'clock (s)', id: 'clocks' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Throughput',
|
||||
formats: [
|
||||
{ name: 'counts/sec (cps)', id: 'cps' },
|
||||
{ name: 'ops/sec (ops)', id: 'ops' },
|
||||
{ name: 'requests/sec (rps)', id: 'reqps' },
|
||||
{ name: 'reads/sec (rps)', id: 'rps' },
|
||||
{ name: 'writes/sec (wps)', id: 'wps' },
|
||||
{ name: 'I/O ops/sec (iops)', id: 'iops' },
|
||||
{ name: 'counts/min (cpm)', id: 'cpm' },
|
||||
{ name: 'ops/min (opm)', id: 'opm' },
|
||||
{ name: 'reads/min (rpm)', id: 'rpm' },
|
||||
{ name: 'writes/min (wpm)', id: 'wpm' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Data',
|
||||
formats: [
|
||||
{ name: 'bytes(IEC)', id: 'bytes' },
|
||||
{ name: 'bytes(SI)', id: 'decbytes' },
|
||||
{ name: 'bits(IEC)', id: 'bits' },
|
||||
{ name: 'bits(SI)', id: 'decbits' },
|
||||
{ name: 'kibibytes', id: 'kbytes' },
|
||||
{ name: 'kilobytes', id: 'deckbytes' },
|
||||
{ name: 'mebibytes', id: 'mbytes' },
|
||||
{ name: 'megabytes', id: 'decmbytes' },
|
||||
{ name: 'gibibytes', id: 'gbytes' },
|
||||
{ name: 'gigabytes', id: 'decgbytes' },
|
||||
{ name: 'tebibytes', id: 'tbytes' },
|
||||
{ name: 'terabytes', id: 'dectbytes' },
|
||||
{ name: 'pebibytes', id: 'pbytes' },
|
||||
{ name: 'petabytes', id: 'decpbytes' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Data rate',
|
||||
formats: [
|
||||
{ name: 'packets/sec', id: 'pps' },
|
||||
{ name: 'bytes/sec(IEC)', id: 'binBps' },
|
||||
{ name: 'bytes/sec(SI)', id: 'Bps' },
|
||||
{ name: 'bits/sec(IEC)', id: 'binbps' },
|
||||
{ name: 'bits/sec(SI)', id: 'bps' },
|
||||
{ name: 'kibibytes/sec', id: 'KiBs' },
|
||||
{ name: 'kibibits/sec', id: 'Kibits' },
|
||||
{ name: 'kilobytes/sec', id: 'KBs' },
|
||||
{ name: 'kilobits/sec', id: 'Kbits' },
|
||||
{ name: 'mebibytes/sec', id: 'MiBs' },
|
||||
{ name: 'mebibits/sec', id: 'Mibits' },
|
||||
{ name: 'megabytes/sec', id: 'MBs' },
|
||||
{ name: 'megabits/sec', id: 'Mbits' },
|
||||
{ name: 'gibibytes/sec', id: 'GiBs' },
|
||||
{ name: 'gibibits/sec', id: 'Gibits' },
|
||||
{ name: 'gigabytes/sec', id: 'GBs' },
|
||||
{ name: 'gigabits/sec', id: 'Gbits' },
|
||||
{ name: 'tebibytes/sec', id: 'TiBs' },
|
||||
{ name: 'tebibits/sec', id: 'Tibits' },
|
||||
{ name: 'terabytes/sec', id: 'TBs' },
|
||||
{ name: 'terabits/sec', id: 'Tbits' },
|
||||
{ name: 'pebibytes/sec', id: 'PiBs' },
|
||||
{ name: 'pebibits/sec', id: 'Pibits' },
|
||||
{ name: 'petabytes/sec', id: 'PBs' },
|
||||
{ name: 'petabits/sec', id: 'Pbits' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Hash rate',
|
||||
formats: [
|
||||
{ name: 'hashes/sec', id: 'Hs' },
|
||||
{ name: 'kilohashes/sec', id: 'KHs' },
|
||||
{ name: 'megahashes/sec', id: 'MHs' },
|
||||
{ name: 'gigahashes/sec', id: 'GHs' },
|
||||
{ name: 'terahashes/sec', id: 'THs' },
|
||||
{ name: 'petahashes/sec', id: 'PHs' },
|
||||
{ name: 'exahashes/sec', id: 'EHs' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Misc',
|
||||
formats: [
|
||||
{ name: 'none', id: 'none' },
|
||||
{ name: 'String', id: 'string' },
|
||||
{ name: 'short', id: 'short' },
|
||||
{ name: 'Percent (0-100)', id: 'percent' },
|
||||
{ name: 'Percent (0.0-1.0)', id: 'percentunit' },
|
||||
{ name: 'Humidity (%H)', id: 'humidity' },
|
||||
{ name: 'Decibel', id: 'dB' },
|
||||
{ name: 'Hexadecimal (0x)', id: 'hex0x' },
|
||||
{ name: 'Hexadecimal', id: 'hex' },
|
||||
{ name: 'Scientific notation', id: 'sci' },
|
||||
{ name: 'Locale format', id: 'locale' },
|
||||
{ name: 'Pixels', id: 'pixel' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Acceleration',
|
||||
formats: [
|
||||
{ name: 'Meters/sec²', id: 'accMS2' },
|
||||
{ name: 'Feet/sec²', id: 'accFS2' },
|
||||
{ name: 'G unit', id: 'accG' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Angle',
|
||||
formats: [
|
||||
{ name: 'Degrees (°)', id: 'degree' },
|
||||
{ name: 'Radians', id: 'radian' },
|
||||
{ name: 'Gradian', id: 'grad' },
|
||||
{ name: 'Arc Minutes', id: 'arcmin' },
|
||||
{ name: 'Arc Seconds', id: 'arcsec' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Area',
|
||||
formats: [
|
||||
{ name: 'Square Meters (m²)', id: 'areaM2' },
|
||||
{ name: 'Square Feet (ft²)', id: 'areaF2' },
|
||||
{ name: 'Square Miles (mi²)', id: 'areaMI2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Computation',
|
||||
formats: [
|
||||
{ name: 'FLOP/s', id: 'flops' },
|
||||
{ name: 'MFLOP/s', id: 'mflops' },
|
||||
{ name: 'GFLOP/s', id: 'gflops' },
|
||||
{ name: 'TFLOP/s', id: 'tflops' },
|
||||
{ name: 'PFLOP/s', id: 'pflops' },
|
||||
{ name: 'EFLOP/s', id: 'eflops' },
|
||||
{ name: 'ZFLOP/s', id: 'zflops' },
|
||||
{ name: 'YFLOP/s', id: 'yflops' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Concentration',
|
||||
formats: [
|
||||
{ name: 'parts-per-million (ppm)', id: 'ppm' },
|
||||
{ name: 'parts-per-billion (ppb)', id: 'conppb' },
|
||||
{ name: 'nanogram per cubic meter (ng/m³)', id: 'conngm3' },
|
||||
{ name: 'nanogram per normal cubic meter (ng/Nm³)', id: 'conngNm3' },
|
||||
{ name: 'microgram per cubic meter (μg/m³)', id: 'conμgm3' },
|
||||
{ name: 'microgram per normal cubic meter (μg/Nm³)', id: 'conμgNm3' },
|
||||
{ name: 'milligram per cubic meter (mg/m³)', id: 'conmgm3' },
|
||||
{ name: 'milligram per normal cubic meter (mg/Nm³)', id: 'conmgNm3' },
|
||||
{ name: 'gram per cubic meter (g/m³)', id: 'congm3' },
|
||||
{ name: 'gram per normal cubic meter (g/Nm³)', id: 'congNm3' },
|
||||
{ name: 'milligrams per decilitre (mg/dL)', id: 'conmgdL' },
|
||||
{ name: 'millimoles per litre (mmol/L)', id: 'conmmolL' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Currency',
|
||||
formats: [
|
||||
{ name: 'Dollars ($)', id: 'currencyUSD' },
|
||||
{ name: 'Pounds (£)', id: 'currencyGBP' },
|
||||
{ name: 'Euro (€)', id: 'currencyEUR' },
|
||||
{ name: 'Yen (¥)', id: 'currencyJPY' },
|
||||
{ name: 'Rubles (₽)', id: 'currencyRUB' },
|
||||
{ name: 'Hryvnias (₴)', id: 'currencyUAH' },
|
||||
{ name: 'Real (R$)', id: 'currencyBRL' },
|
||||
{ name: 'Danish Krone (kr)', id: 'currencyDKK' },
|
||||
{ name: 'Icelandic Króna (kr)', id: 'currencyISK' },
|
||||
{ name: 'Norwegian Krone (kr)', id: 'currencyNOK' },
|
||||
{ name: 'Swedish Krona (kr)', id: 'currencySEK' },
|
||||
{ name: 'Czech koruna (czk)', id: 'currencyCZK' },
|
||||
{ name: 'Swiss franc (CHF)', id: 'currencyCHF' },
|
||||
{ name: 'Polish Złoty (PLN)', id: 'currencyPLN' },
|
||||
{ name: 'Bitcoin (฿)', id: 'currencyBTC' },
|
||||
{ name: 'Milli Bitcoin (฿)', id: 'currencymBTC' },
|
||||
{ name: 'Micro Bitcoin (฿)', id: 'currencyμBTC' },
|
||||
{ name: 'South African Rand (R)', id: 'currencyZAR' },
|
||||
{ name: 'Indian Rupee (₹)', id: 'currencyINR' },
|
||||
{ name: 'South Korean Won (₩)', id: 'currencyKRW' },
|
||||
{ name: 'Indonesian Rupiah (Rp)', id: 'currencyIDR' },
|
||||
{ name: 'Philippine Peso (PHP)', id: 'currencyPHP' },
|
||||
{ name: 'Vietnamese Dong (VND)', id: 'currencyVND' },
|
||||
],
|
||||
},
|
||||
import {
|
||||
AccelerationFormats,
|
||||
AngularFormats,
|
||||
AreaFormats,
|
||||
BooleanFormats,
|
||||
CategoryNames,
|
||||
ConcentrationFormats,
|
||||
CurrencyFormats,
|
||||
DataFormats,
|
||||
DataRateFormats,
|
||||
DataTypeCategories,
|
||||
DatetimeFormats,
|
||||
FlopsFormats,
|
||||
FlowFormats,
|
||||
ForceFormats,
|
||||
HashRateFormats,
|
||||
LengthFormats,
|
||||
MassFormats,
|
||||
MiscellaneousFormats,
|
||||
PowerElectricalFormats,
|
||||
PressureFormats,
|
||||
RadiationFormats,
|
||||
RotationSpeedFormats,
|
||||
TemperatureFormats,
|
||||
ThroughputFormats,
|
||||
TimeFormats,
|
||||
VelocityFormats,
|
||||
VolumeFormats,
|
||||
} from './types';
|
||||
|
||||
export const dataTypeCategories: DataTypeCategories = [
|
||||
{
|
||||
name: 'Date & time',
|
||||
name: CategoryNames.Time,
|
||||
formats: [
|
||||
{ name: 'Datetime ISO', id: 'dateTimeAsIso' },
|
||||
{ name: 'Hertz (1/s)', id: TimeFormats.Hertz },
|
||||
{ name: 'nanoseconds (ns)', id: TimeFormats.Nanoseconds },
|
||||
{ name: 'microseconds (µs)', id: TimeFormats.Microseconds },
|
||||
{ name: 'milliseconds (ms)', id: TimeFormats.Milliseconds },
|
||||
{ name: 'seconds (s)', id: TimeFormats.Seconds },
|
||||
{ name: 'minutes (m)', id: TimeFormats.Minutes },
|
||||
{ name: 'hours (h)', id: TimeFormats.Hours },
|
||||
{ name: 'days (d)', id: TimeFormats.Days },
|
||||
{ name: 'duration in ms (dtdurationms)', id: TimeFormats.DurationMs },
|
||||
{ name: 'duration in s (dtdurations)', id: TimeFormats.DurationS },
|
||||
{ name: 'duration in h:m:s (dthms)', id: TimeFormats.DurationHms },
|
||||
{ name: 'duration in d:h:m:s (dtdhms)', id: TimeFormats.DurationDhms },
|
||||
{ name: 'timeticks (timeticks)', id: TimeFormats.Timeticks },
|
||||
{ name: 'clock in ms (clockms)', id: TimeFormats.ClockMs },
|
||||
{ name: 'clock in s (clocks)', id: TimeFormats.ClockS },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Throughput,
|
||||
formats: [
|
||||
{ name: 'counts/sec (cps)', id: ThroughputFormats.CountsPerSec },
|
||||
{ name: 'ops/sec (ops)', id: ThroughputFormats.OpsPerSec },
|
||||
{ name: 'requests/sec (reqps)', id: ThroughputFormats.RequestsPerSec },
|
||||
{ name: 'reads/sec (rps)', id: ThroughputFormats.ReadsPerSec },
|
||||
{ name: 'writes/sec (wps)', id: ThroughputFormats.WritesPerSec },
|
||||
{ name: 'I/O operations/sec (iops)', id: ThroughputFormats.IOOpsPerSec },
|
||||
{ name: 'counts/min (cpm)', id: ThroughputFormats.CountsPerMin },
|
||||
{ name: 'ops/min (opm)', id: ThroughputFormats.OpsPerMin },
|
||||
{ name: 'reads/min (rpm)', id: ThroughputFormats.ReadsPerMin },
|
||||
{ name: 'writes/min (wpm)', id: ThroughputFormats.WritesPerMin },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Data,
|
||||
formats: [
|
||||
{ name: 'bytes(IEC)', id: DataFormats.BytesIEC },
|
||||
{ name: 'bytes(SI)', id: DataFormats.BytesSI },
|
||||
{ name: 'bits(IEC)', id: DataFormats.BitsIEC },
|
||||
{ name: 'bits(SI)', id: DataFormats.BitsSI },
|
||||
{ name: 'kibibytes', id: DataFormats.KibiBytes },
|
||||
{ name: 'kilobytes', id: DataFormats.KiloBytes },
|
||||
{ name: 'mebibytes', id: DataFormats.MebiBytes },
|
||||
{ name: 'megabytes', id: DataFormats.MegaBytes },
|
||||
{ name: 'gibibytes', id: DataFormats.GibiBytes },
|
||||
{ name: 'gigabytes', id: DataFormats.GigaBytes },
|
||||
{ name: 'tebibytes', id: DataFormats.TebiBytes },
|
||||
{ name: 'terabytes', id: DataFormats.TeraBytes },
|
||||
{ name: 'pebibytes', id: DataFormats.PebiBytes },
|
||||
{ name: 'petabytes', id: DataFormats.PetaBytes },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.DataRate,
|
||||
formats: [
|
||||
{ name: 'packets/sec', id: DataRateFormats.PacketsPerSec },
|
||||
{ name: 'bytes/sec(IEC)', id: DataRateFormats.BytesPerSecIEC },
|
||||
{ name: 'bytes/sec(SI)', id: DataRateFormats.BytesPerSecSI },
|
||||
{ name: 'bits/sec(IEC)', id: DataRateFormats.BitsPerSecIEC },
|
||||
{ name: 'bits/sec(SI)', id: DataRateFormats.BitsPerSecSI },
|
||||
{ name: 'kibibytes/sec', id: DataRateFormats.KibiBytesPerSec },
|
||||
{ name: 'kibibits/sec', id: DataRateFormats.KibiBitsPerSec },
|
||||
{ name: 'kilobytes/sec', id: DataRateFormats.KiloBytesPerSec },
|
||||
{ name: 'kilobits/sec', id: DataRateFormats.KiloBitsPerSec },
|
||||
{ name: 'mebibytes/sec', id: DataRateFormats.MebiBytesPerSec },
|
||||
{ name: 'mebibits/sec', id: DataRateFormats.MebiBitsPerSec },
|
||||
{ name: 'megabytes/sec', id: DataRateFormats.MegaBytesPerSec },
|
||||
{ name: 'megabits/sec', id: DataRateFormats.MegaBitsPerSec },
|
||||
{ name: 'gibibytes/sec', id: DataRateFormats.GibiBytesPerSec },
|
||||
{ name: 'gibibits/sec', id: DataRateFormats.GibiBitsPerSec },
|
||||
{ name: 'gigabytes/sec', id: DataRateFormats.GigaBytesPerSec },
|
||||
{ name: 'gigabits/sec', id: DataRateFormats.GigaBitsPerSec },
|
||||
{ name: 'tebibytes/sec', id: DataRateFormats.TebiBytesPerSec },
|
||||
{ name: 'tebibits/sec', id: DataRateFormats.TebiBitsPerSec },
|
||||
{ name: 'terabytes/sec', id: DataRateFormats.TeraBytesPerSec },
|
||||
{ name: 'terabits/sec', id: DataRateFormats.TeraBitsPerSec },
|
||||
{ name: 'pebibytes/sec', id: DataRateFormats.PebiBytesPerSec },
|
||||
{ name: 'pebibits/sec', id: DataRateFormats.PebiBitsPerSec },
|
||||
{ name: 'petabytes/sec', id: DataRateFormats.PetaBytesPerSec },
|
||||
{ name: 'petabits/sec', id: DataRateFormats.PetaBitsPerSec },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.HashRate,
|
||||
formats: [
|
||||
{ name: 'hashes/sec', id: HashRateFormats.HashesPerSec },
|
||||
{ name: 'kilohashes/sec', id: HashRateFormats.KiloHashesPerSec },
|
||||
{ name: 'megahashes/sec', id: HashRateFormats.MegaHashesPerSec },
|
||||
{ name: 'gigahashes/sec', id: HashRateFormats.GigaHashesPerSec },
|
||||
{ name: 'terahashes/sec', id: HashRateFormats.TeraHashesPerSec },
|
||||
{ name: 'petahashes/sec', id: HashRateFormats.PetaHashesPerSec },
|
||||
{ name: 'exahashes/sec', id: HashRateFormats.ExaHashesPerSec },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Miscellaneous,
|
||||
formats: [
|
||||
{ name: 'none', id: MiscellaneousFormats.None },
|
||||
{ name: 'String', id: MiscellaneousFormats.String },
|
||||
{ name: 'short', id: MiscellaneousFormats.Short },
|
||||
{ name: 'Percent (0-100)', id: MiscellaneousFormats.Percent },
|
||||
{ name: 'Percent (0.0-1.0)', id: MiscellaneousFormats.PercentUnit },
|
||||
{ name: 'Humidity (%H)', id: MiscellaneousFormats.Humidity },
|
||||
{ name: 'Decibel', id: MiscellaneousFormats.Decibel },
|
||||
{ name: 'Hexadecimal (0x)', id: MiscellaneousFormats.Hexadecimal0x },
|
||||
{ name: 'Hexadecimal', id: MiscellaneousFormats.Hexadecimal },
|
||||
{ name: 'Scientific notation', id: MiscellaneousFormats.ScientificNotation },
|
||||
{ name: 'Locale format', id: MiscellaneousFormats.LocaleFormat },
|
||||
{ name: 'Pixels', id: MiscellaneousFormats.Pixels },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Acceleration,
|
||||
formats: [
|
||||
{ name: 'Meters/sec²', id: AccelerationFormats.MetersPerSecondSquared },
|
||||
{ name: 'Feet/sec²', id: AccelerationFormats.FeetPerSecondSquared },
|
||||
{ name: 'G unit', id: AccelerationFormats.GUnit },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Angle,
|
||||
formats: [
|
||||
{ name: 'Degrees (°)', id: AngularFormats.Degree },
|
||||
{ name: 'Radians', id: AngularFormats.Radian },
|
||||
{ name: 'Gradian', id: AngularFormats.Gradian },
|
||||
{ name: 'Arc Minutes', id: AngularFormats.ArcMinute },
|
||||
{ name: 'Arc Seconds', id: AngularFormats.ArcSecond },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Area,
|
||||
formats: [
|
||||
{ name: 'Square Meters (m²)', id: AreaFormats.SquareMeters },
|
||||
{ name: 'Square Feet (ft²)', id: AreaFormats.SquareFeet },
|
||||
{ name: 'Square Miles (mi²)', id: AreaFormats.SquareMiles },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Computation,
|
||||
formats: [
|
||||
{ name: 'FLOP/s', id: FlopsFormats.FLOPs },
|
||||
{ name: 'MFLOP/s', id: FlopsFormats.MFLOPs },
|
||||
{ name: 'GFLOP/s', id: FlopsFormats.GFLOPs },
|
||||
{ name: 'TFLOP/s', id: FlopsFormats.TFLOPs },
|
||||
{ name: 'PFLOP/s', id: FlopsFormats.PFLOPs },
|
||||
{ name: 'EFLOP/s', id: FlopsFormats.EFLOPs },
|
||||
{ name: 'ZFLOP/s', id: FlopsFormats.ZFLOPs },
|
||||
{ name: 'YFLOP/s', id: FlopsFormats.YFLOPs },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Concentration,
|
||||
formats: [
|
||||
{ name: 'parts-per-million (ppm)', id: ConcentrationFormats.PPM },
|
||||
{ name: 'parts-per-billion (ppb)', id: ConcentrationFormats.PPB },
|
||||
{ name: 'nanogram per cubic meter (ng/m³)', id: ConcentrationFormats.NgM3 },
|
||||
{
|
||||
name: 'nanogram per normal cubic meter (ng/Nm³)',
|
||||
id: ConcentrationFormats.NgNM3,
|
||||
},
|
||||
{ name: 'microgram per cubic meter (μg/m³)', id: ConcentrationFormats.UgM3 },
|
||||
{
|
||||
name: 'microgram per normal cubic meter (μg/Nm³)',
|
||||
id: ConcentrationFormats.UgNM3,
|
||||
},
|
||||
{ name: 'milligram per cubic meter (mg/m³)', id: ConcentrationFormats.MgM3 },
|
||||
{
|
||||
name: 'milligram per normal cubic meter (mg/Nm³)',
|
||||
id: ConcentrationFormats.MgNM3,
|
||||
},
|
||||
{ name: 'gram per cubic meter (g/m³)', id: ConcentrationFormats.GM3 },
|
||||
{
|
||||
name: 'gram per normal cubic meter (g/Nm³)',
|
||||
id: ConcentrationFormats.GNM3,
|
||||
},
|
||||
{ name: 'milligrams per decilitre (mg/dL)', id: ConcentrationFormats.MgDL },
|
||||
{ name: 'millimoles per litre (mmol/L)', id: ConcentrationFormats.MmolL },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Currency,
|
||||
formats: [
|
||||
{ name: 'Dollars ($)', id: CurrencyFormats.USD },
|
||||
{ name: 'Pounds (£)', id: CurrencyFormats.GBP },
|
||||
{ name: 'Euro (€)', id: CurrencyFormats.EUR },
|
||||
{ name: 'Yen (¥)', id: CurrencyFormats.JPY },
|
||||
{ name: 'Rubles (₽)', id: CurrencyFormats.RUB },
|
||||
{ name: 'Hryvnias (₴)', id: CurrencyFormats.UAH },
|
||||
{ name: 'Real (R$)', id: CurrencyFormats.BRL },
|
||||
{ name: 'Danish Krone (kr)', id: CurrencyFormats.DKK },
|
||||
{ name: 'Icelandic Króna (kr)', id: CurrencyFormats.ISK },
|
||||
{ name: 'Norwegian Krone (kr)', id: CurrencyFormats.NOK },
|
||||
{ name: 'Swedish Krona (kr)', id: CurrencyFormats.SEK },
|
||||
{ name: 'Czech koruna (czk)', id: CurrencyFormats.CZK },
|
||||
{ name: 'Swiss franc (CHF)', id: CurrencyFormats.CHF },
|
||||
{ name: 'Polish Złoty (PLN)', id: CurrencyFormats.PLN },
|
||||
{ name: 'Bitcoin (฿)', id: CurrencyFormats.BTC },
|
||||
{ name: 'Milli Bitcoin (฿)', id: CurrencyFormats.MBTC },
|
||||
{ name: 'Micro Bitcoin (฿)', id: CurrencyFormats.UBTC },
|
||||
{ name: 'South African Rand (R)', id: CurrencyFormats.ZAR },
|
||||
{ name: 'Indian Rupee (₹)', id: CurrencyFormats.INR },
|
||||
{ name: 'South Korean Won (₩)', id: CurrencyFormats.KRW },
|
||||
{ name: 'Indonesian Rupiah (Rp)', id: CurrencyFormats.IDR },
|
||||
{ name: 'Philippine Peso (PHP)', id: CurrencyFormats.PHP },
|
||||
{ name: 'Vietnamese Dong (VND)', id: CurrencyFormats.VND },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: CategoryNames.Datetime,
|
||||
formats: [
|
||||
{ name: 'Datetime ISO', id: DatetimeFormats.ISO },
|
||||
{
|
||||
name: 'Datetime ISO (No date if today)',
|
||||
id: 'dateTimeAsIsoNoDateIfToday',
|
||||
id: DatetimeFormats.ISONoDateIfToday,
|
||||
},
|
||||
{ name: 'Datetime US', id: 'dateTimeAsUS' },
|
||||
{ name: 'Datetime US (No date if today)', id: 'dateTimeAsUSNoDateIfToday' },
|
||||
{ name: 'Datetime local', id: 'dateTimeAsLocal' },
|
||||
{ name: 'Datetime US', id: DatetimeFormats.US },
|
||||
{
|
||||
name: 'Datetime US (No date if today)',
|
||||
id: DatetimeFormats.USNoDateIfToday,
|
||||
},
|
||||
{ name: 'Datetime local', id: DatetimeFormats.Local },
|
||||
{
|
||||
name: 'Datetime local (No date if today)',
|
||||
id: 'dateTimeAsLocalNoDateIfToday',
|
||||
id: DatetimeFormats.LocalNoDateIfToday,
|
||||
},
|
||||
{ name: 'Datetime default', id: 'dateTimeAsSystem' },
|
||||
{ name: 'From Now', id: 'dateTimeFromNow' },
|
||||
{ name: 'Datetime default', id: DatetimeFormats.System },
|
||||
{ name: 'From Now', id: DatetimeFormats.FromNow },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Energy',
|
||||
name: CategoryNames.Energy,
|
||||
formats: [
|
||||
{ name: 'Watt (W)', id: 'watt' },
|
||||
{ name: 'Kilowatt (kW)', id: 'kwatt' },
|
||||
{ name: 'Megawatt (MW)', id: 'megwatt' },
|
||||
{ name: 'Gigawatt (GW)', id: 'gwatt' },
|
||||
{ name: 'Milliwatt (mW)', id: 'mwatt' },
|
||||
{ name: 'Watt per square meter (W/m²)', id: 'Wm2' },
|
||||
{ name: 'Volt-Ampere (VA)', id: 'voltamp' },
|
||||
{ name: 'Kilovolt-Ampere (kVA)', id: 'kvoltamp' },
|
||||
{ name: 'Volt-Ampere reactive (VAr)', id: 'voltampreact' },
|
||||
{ name: 'Kilovolt-Ampere reactive (kVAr)', id: 'kvoltampreact' },
|
||||
{ name: 'Watt-hour (Wh)', id: 'watth' },
|
||||
{ name: 'Watt-hour per Kilogram (Wh/kg)', id: 'watthperkg' },
|
||||
{ name: 'Kilowatt-hour (kWh)', id: 'kwatth' },
|
||||
{ name: 'Kilowatt-min (kWm)', id: 'kwattm' },
|
||||
{ name: 'Ampere-hour (Ah)', id: 'amph' },
|
||||
{ name: 'Kiloampere-hour (kAh)', id: 'kamph' },
|
||||
{ name: 'Milliampere-hour (mAh)', id: 'mamph' },
|
||||
{ name: 'Joule (J)', id: 'joule' },
|
||||
{ name: 'Electron volt (eV)', id: 'ev' },
|
||||
{ name: 'Ampere (A)', id: 'amp' },
|
||||
{ name: 'Kiloampere (kA)', id: 'kamp' },
|
||||
{ name: 'Milliampere (mA)', id: 'mamp' },
|
||||
{ name: 'Volt (V)', id: 'volt' },
|
||||
{ name: 'Kilovolt (kV)', id: 'kvolt' },
|
||||
{ name: 'Millivolt (mV)', id: 'mvolt' },
|
||||
{ name: 'Decibel-milliwatt (dBm)', id: 'dBm' },
|
||||
{ name: 'Ohm (Ω)', id: 'ohm' },
|
||||
{ name: 'Kiloohm (kΩ)', id: 'kohm' },
|
||||
{ name: 'Megaohm (MΩ)', id: 'Mohm' },
|
||||
{ name: 'Farad (F)', id: 'farad' },
|
||||
{ name: 'Microfarad (µF)', id: 'µfarad' },
|
||||
{ name: 'Nanofarad (nF)', id: 'nfarad' },
|
||||
{ name: 'Picofarad (pF)', id: 'pfarad' },
|
||||
{ name: 'Femtofarad (fF)', id: 'ffarad' },
|
||||
{ name: 'Henry (H)', id: 'henry' },
|
||||
{ name: 'Millihenry (mH)', id: 'mhenry' },
|
||||
{ name: 'Microhenry (µH)', id: 'µhenry' },
|
||||
{ name: 'Lumens (Lm)', id: 'lumens' },
|
||||
{ name: 'Watt (W)', id: PowerElectricalFormats.WATT },
|
||||
{ name: 'Kilowatt (kW)', id: PowerElectricalFormats.KWATT },
|
||||
{ name: 'Megawatt (MW)', id: PowerElectricalFormats.MEGWATT },
|
||||
{ name: 'Gigawatt (GW)', id: PowerElectricalFormats.GWATT },
|
||||
{ name: 'Milliwatt (mW)', id: PowerElectricalFormats.MWATT },
|
||||
{ name: 'Watt per square meter (W/m²)', id: PowerElectricalFormats.WM2 },
|
||||
{ name: 'Volt-Ampere (VA)', id: PowerElectricalFormats.VOLTAMP },
|
||||
{ name: 'Kilovolt-Ampere (kVA)', id: PowerElectricalFormats.KVOLTAMP },
|
||||
{
|
||||
name: 'Volt-Ampere reactive (VAr)',
|
||||
id: PowerElectricalFormats.VOLTAMPREACT,
|
||||
},
|
||||
{
|
||||
name: 'Kilovolt-Ampere reactive (kVAr)',
|
||||
id: PowerElectricalFormats.KVOLTAMPREACT,
|
||||
},
|
||||
{ name: 'Watt-hour (Wh)', id: PowerElectricalFormats.WATTH },
|
||||
{
|
||||
name: 'Watt-hour per Kilogram (Wh/kg)',
|
||||
id: PowerElectricalFormats.WATTHPERKG,
|
||||
},
|
||||
{ name: 'Kilowatt-hour (kWh)', id: PowerElectricalFormats.KWATTH },
|
||||
{ name: 'Kilowatt-min (kWm)', id: PowerElectricalFormats.KWATTM },
|
||||
{ name: 'Ampere-hour (Ah)', id: PowerElectricalFormats.AMPH },
|
||||
{ name: 'Kiloampere-hour (kAh)', id: PowerElectricalFormats.KAMPH },
|
||||
{ name: 'Milliampere-hour (mAh)', id: PowerElectricalFormats.MAMPH },
|
||||
{ name: 'Joule (J)', id: PowerElectricalFormats.JOULE },
|
||||
{ name: 'Electron volt (eV)', id: PowerElectricalFormats.EV },
|
||||
{ name: 'Ampere (A)', id: PowerElectricalFormats.AMP },
|
||||
{ name: 'Kiloampere (kA)', id: PowerElectricalFormats.KAMP },
|
||||
{ name: 'Milliampere (mA)', id: PowerElectricalFormats.MAMP },
|
||||
{ name: 'Volt (V)', id: PowerElectricalFormats.VOLT },
|
||||
{ name: 'Kilovolt (kV)', id: PowerElectricalFormats.KVOLT },
|
||||
{ name: 'Millivolt (mV)', id: PowerElectricalFormats.MVOLT },
|
||||
{ name: 'Decibel-milliwatt (dBm)', id: PowerElectricalFormats.DBM },
|
||||
{ name: 'Ohm (Ω)', id: PowerElectricalFormats.OHM },
|
||||
{ name: 'Kiloohm (kΩ)', id: PowerElectricalFormats.KOHM },
|
||||
{ name: 'Megaohm (MΩ)', id: PowerElectricalFormats.MOHM },
|
||||
{ name: 'Farad (F)', id: PowerElectricalFormats.FARAD },
|
||||
{ name: 'Microfarad (µF)', id: PowerElectricalFormats.µFARAD },
|
||||
{ name: 'Nanofarad (nF)', id: PowerElectricalFormats.NFARAD },
|
||||
{ name: 'Picofarad (pF)', id: PowerElectricalFormats.PFARAD },
|
||||
{ name: 'Femtofarad (fF)', id: PowerElectricalFormats.FFARAD },
|
||||
{ name: 'Henry (H)', id: PowerElectricalFormats.HENRY },
|
||||
{ name: 'Millihenry (mH)', id: PowerElectricalFormats.MHENRY },
|
||||
{ name: 'Microhenry (µH)', id: PowerElectricalFormats.µHENRY },
|
||||
{ name: 'Lumens (Lm)', id: PowerElectricalFormats.LUMENS },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Flow',
|
||||
name: CategoryNames.Flow,
|
||||
formats: [
|
||||
{ name: 'Gallons/min (gpm)', id: 'flowgpm' },
|
||||
{ name: 'Cubic meters/sec (cms)', id: 'flowcms' },
|
||||
{ name: 'Cubic feet/sec (cfs)', id: 'flowcfs' },
|
||||
{ name: 'Cubic feet/min (cfm)', id: 'flowcfm' },
|
||||
{ name: 'Litre/hour', id: 'litreh' },
|
||||
{ name: 'Litre/min (L/min)', id: 'flowlpm' },
|
||||
{ name: 'milliLitre/min (mL/min)', id: 'flowmlpm' },
|
||||
{ name: 'Lux (lx)', id: 'lux' },
|
||||
{ name: 'Gallons/min (gpm)', id: FlowFormats.FLOWGPM },
|
||||
{ name: 'Cubic meters/sec (cms)', id: FlowFormats.FLOWCMS },
|
||||
{ name: 'Cubic feet/sec (cfs)', id: FlowFormats.FLOWCFS },
|
||||
{ name: 'Cubic feet/min (cfm)', id: FlowFormats.FLOWCFM },
|
||||
{ name: 'Litre/hour', id: FlowFormats.LITREH },
|
||||
{ name: 'Litre/min (L/min)', id: FlowFormats.FLOWLPM },
|
||||
{ name: 'milliLitre/min (mL/min)', id: FlowFormats.FLOWMLPM },
|
||||
{ name: 'Lux (lx)', id: FlowFormats.LUX },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Force',
|
||||
name: CategoryNames.Force,
|
||||
formats: [
|
||||
{ name: 'Newton-meters (Nm)', id: 'forceNm' },
|
||||
{ name: 'Kilonewton-meters (kNm)', id: 'forcekNm' },
|
||||
{ name: 'Newtons (N)', id: 'forceN' },
|
||||
{ name: 'Kilonewtons (kN)', id: 'forcekN' },
|
||||
{ name: 'Newton-meters (Nm)', id: ForceFormats.FORCENM },
|
||||
{ name: 'Kilonewton-meters (kNm)', id: ForceFormats.FORCEKNM },
|
||||
{ name: 'Newtons (N)', id: ForceFormats.FORCEN },
|
||||
{ name: 'Kilonewtons (kN)', id: ForceFormats.FORCEKN },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Mass',
|
||||
name: CategoryNames.Mass,
|
||||
formats: [
|
||||
{ name: 'milligram (mg)', id: 'massmg' },
|
||||
{ name: 'gram (g)', id: 'massg' },
|
||||
{ name: 'pound (lb)', id: 'masslb' },
|
||||
{ name: 'kilogram (kg)', id: 'masskg' },
|
||||
{ name: 'metric ton (t)', id: 'masst' },
|
||||
{ name: 'milligram (mg)', id: MassFormats.MASSMG },
|
||||
{ name: 'gram (g)', id: MassFormats.MASSG },
|
||||
{ name: 'pound (lb)', id: MassFormats.MASSLB },
|
||||
{ name: 'kilogram (kg)', id: MassFormats.MASSKG },
|
||||
{ name: 'metric ton (t)', id: MassFormats.MASST },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Length',
|
||||
name: CategoryNames.Length,
|
||||
formats: [
|
||||
{ name: 'millimeter (mm)', id: 'lengthmm' },
|
||||
{ name: 'inch (in)', id: 'lengthin' },
|
||||
{ name: 'feet (ft)', id: 'lengthft' },
|
||||
{ name: 'meter (m)', id: 'lengthm' },
|
||||
{ name: 'kilometer (km)', id: 'lengthkm' },
|
||||
{ name: 'mile (mi)', id: 'lengthmi' },
|
||||
{ name: 'millimeter (mm)', id: LengthFormats.LENGTHMM },
|
||||
{ name: 'inch (in)', id: LengthFormats.LENGTHIN },
|
||||
{ name: 'feet (ft)', id: LengthFormats.LENGTHFT },
|
||||
{ name: 'meter (m)', id: LengthFormats.LENGTHM },
|
||||
{ name: 'kilometer (km)', id: LengthFormats.LENGTHKM },
|
||||
{ name: 'mile (mi)', id: LengthFormats.LENGTHMI },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Pressure',
|
||||
name: CategoryNames.Pressure,
|
||||
formats: [
|
||||
{ name: 'Millibars', id: 'pressurembar' },
|
||||
{ name: 'Bars', id: 'pressurebar' },
|
||||
{ name: 'Kilobars', id: 'pressurekbar' },
|
||||
{ name: 'Pascals', id: 'pressurepa' },
|
||||
{ name: 'Hectopascals', id: 'pressurehpa' },
|
||||
{ name: 'Kilopascals', id: 'pressurekpa' },
|
||||
{ name: 'Inches of mercury', id: 'pressurehg' },
|
||||
{ name: 'PSI', id: 'pressurepsi' },
|
||||
{ name: 'Millibars', id: PressureFormats.PRESSUREMBAR },
|
||||
{ name: 'Bars', id: PressureFormats.PRESSUREBAR },
|
||||
{ name: 'Kilobars', id: PressureFormats.PRESSUREKBAR },
|
||||
{ name: 'Pascals', id: PressureFormats.PRESSUREPA },
|
||||
{ name: 'Hectopascals', id: PressureFormats.PRESSUREHPA },
|
||||
{ name: 'Kilopascals', id: PressureFormats.PRESSUREKPA },
|
||||
{ name: 'Inches of mercury', id: PressureFormats.PRESSUREHG },
|
||||
{ name: 'PSI', id: PressureFormats.PRESSUREPSI },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Radiation',
|
||||
name: CategoryNames.Radiation,
|
||||
formats: [
|
||||
{ name: 'Becquerel (Bq)', id: 'radbq' },
|
||||
{ name: 'curie (Ci)', id: 'radci' },
|
||||
{ name: 'Gray (Gy)', id: 'radgy' },
|
||||
{ name: 'rad', id: 'radrad' },
|
||||
{ name: 'Sievert (Sv)', id: 'radsv' },
|
||||
{ name: 'milliSievert (mSv)', id: 'radmsv' },
|
||||
{ name: 'microSievert (µSv)', id: 'radusv' },
|
||||
{ name: 'rem', id: 'radrem' },
|
||||
{ name: 'Exposure (C/kg)', id: 'radexpckg' },
|
||||
{ name: 'roentgen (R)', id: 'radr' },
|
||||
{ name: 'Sievert/hour (Sv/h)', id: 'radsvh' },
|
||||
{ name: 'milliSievert/hour (mSv/h)', id: 'radmsvh' },
|
||||
{ name: 'microSievert/hour (µSv/h)', id: 'radusvh' },
|
||||
{ name: 'Becquerel (Bq)', id: RadiationFormats.RADBQ },
|
||||
{ name: 'curie (Ci)', id: RadiationFormats.RADCI },
|
||||
{ name: 'Gray (Gy)', id: RadiationFormats.RADGY },
|
||||
{ name: 'rad', id: RadiationFormats.RADRAD },
|
||||
{ name: 'Sievert (Sv)', id: RadiationFormats.RADSV },
|
||||
{ name: 'milliSievert (mSv)', id: RadiationFormats.RADMSV },
|
||||
{ name: 'microSievert (µSv)', id: RadiationFormats.RADUSV },
|
||||
{ name: 'rem', id: RadiationFormats.RADREM },
|
||||
{ name: 'Exposure (C/kg)', id: RadiationFormats.RADEXPCKG },
|
||||
{ name: 'roentgen (R)', id: RadiationFormats.RADR },
|
||||
{ name: 'Sievert/hour (Sv/h)', id: RadiationFormats.RADSVH },
|
||||
{ name: 'milliSievert/hour (mSv/h)', id: RadiationFormats.RADMSVH },
|
||||
{ name: 'microSievert/hour (µSv/h)', id: RadiationFormats.RADUSVH },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Rotational Speed',
|
||||
name: CategoryNames.RotationSpeed,
|
||||
formats: [
|
||||
{ name: 'Revolutions per minute (rpm)', id: 'rotrpm' },
|
||||
{ name: 'Hertz (Hz)', id: 'rothz' },
|
||||
{ name: 'Radians per second (rad/s)', id: 'rotrads' },
|
||||
{ name: 'Degrees per second (°/s)', id: 'rotdegs' },
|
||||
{ name: 'Revolutions per minute (rpm)', id: RotationSpeedFormats.ROTRPM },
|
||||
{ name: 'Hertz (Hz)', id: RotationSpeedFormats.ROTHZ },
|
||||
{ name: 'Radians per second (rad/s)', id: RotationSpeedFormats.ROTRADS },
|
||||
{ name: 'Degrees per second (°/s)', id: RotationSpeedFormats.ROTDEGS },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Temperature',
|
||||
name: CategoryNames.Temperature,
|
||||
formats: [
|
||||
{ name: 'Celsius (°C)', id: 'celsius' },
|
||||
{ name: 'Fahrenheit (°F)', id: 'fahrenheit' },
|
||||
{ name: 'Kelvin (K)', id: 'kelvin' },
|
||||
{ name: 'Celsius (°C)', id: TemperatureFormats.CELSIUS },
|
||||
{ name: 'Fahrenheit (°F)', id: TemperatureFormats.FAHRENHEIT },
|
||||
{ name: 'Kelvin (K)', id: TemperatureFormats.KELVIN },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Velocity',
|
||||
name: CategoryNames.Velocity,
|
||||
formats: [
|
||||
{ name: 'meters/second (m/s)', id: 'velocityms' },
|
||||
{ name: 'kilometers/hour (km/h)', id: 'velocitykmh' },
|
||||
{ name: 'miles/hour (mph)', id: 'velocitymph' },
|
||||
{ name: 'knot (kn)', id: 'velocityknot' },
|
||||
{ name: 'meters/second (m/s)', id: VelocityFormats.METERS_PER_SECOND },
|
||||
{ name: 'kilometers/hour (km/h)', id: VelocityFormats.KILOMETERS_PER_HOUR },
|
||||
{ name: 'miles/hour (mph)', id: VelocityFormats.MILES_PER_HOUR },
|
||||
{ name: 'knot (kn)', id: VelocityFormats.KNOT },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Volume',
|
||||
name: CategoryNames.Volume,
|
||||
formats: [
|
||||
{ name: 'millilitre (mL)', id: 'mlitre' },
|
||||
{ name: 'litre (L)', id: 'litre' },
|
||||
{ name: 'cubic meter', id: 'm3' },
|
||||
{ name: 'Normal cubic meter', id: 'Nm3' },
|
||||
{ name: 'cubic decimeter', id: 'dm3' },
|
||||
{ name: 'gallons', id: 'gallons' },
|
||||
{ name: 'millilitre (mL)', id: VolumeFormats.MILLILITRE },
|
||||
{ name: 'litre (L)', id: VolumeFormats.LITRE },
|
||||
{ name: 'cubic meter', id: VolumeFormats.CUBIC_METER },
|
||||
{ name: 'Normal cubic meter', id: VolumeFormats.NORMAL_CUBIC_METER },
|
||||
{ name: 'cubic decimeter', id: VolumeFormats.CUBIC_DECIMETER },
|
||||
{ name: 'gallons', id: VolumeFormats.GALLONS },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Boolean',
|
||||
name: CategoryNames.Boolean,
|
||||
formats: [
|
||||
{ name: 'True / False', id: 'bool' },
|
||||
{ name: 'Yes / No', id: 'bool_yes_no' },
|
||||
{ name: 'On / Off', id: 'bool_on_off' },
|
||||
{ name: 'True / False', id: BooleanFormats.TRUE_FALSE },
|
||||
{ name: 'Yes / No', id: BooleanFormats.YES_NO },
|
||||
{ name: 'On / Off', id: BooleanFormats.ON_OFF },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
364
frontend/src/container/NewWidget/RightContainer/types.ts
Normal file
364
frontend/src/container/NewWidget/RightContainer/types.ts
Normal file
@ -0,0 +1,364 @@
|
||||
export enum CategoryNames {
|
||||
Time = 'Time',
|
||||
Throughput = 'Throughput',
|
||||
Data = 'Data',
|
||||
DataRate = 'DataRate',
|
||||
HashRate = 'HashRate',
|
||||
Miscellaneous = 'Miscellaneous',
|
||||
Acceleration = 'Acceleration',
|
||||
Angular = 'Angular',
|
||||
Area = 'Area',
|
||||
Flops = 'Flops',
|
||||
Concentration = 'Concentration',
|
||||
Currency = 'Currency',
|
||||
Datetime = 'Datetime',
|
||||
PowerElectrical = 'PowerElectrical',
|
||||
Flow = 'Flow',
|
||||
Force = 'Force',
|
||||
Mass = 'Mass',
|
||||
Length = 'Length',
|
||||
Pressure = 'Pressure',
|
||||
Radiation = 'Radiation',
|
||||
RotationSpeed = 'RotationSpeed',
|
||||
Temperature = 'Temperature',
|
||||
Velocity = 'Velocity',
|
||||
Volume = 'Volume',
|
||||
Boolean = 'Boolean',
|
||||
Angle = 'Angle',
|
||||
Computation = 'Computation',
|
||||
Energy = 'Energy',
|
||||
}
|
||||
|
||||
export enum TimeFormats {
|
||||
Hertz = 'hertz',
|
||||
Nanoseconds = 'ns',
|
||||
Microseconds = 'µs',
|
||||
Milliseconds = 'ms',
|
||||
Seconds = 's',
|
||||
Minutes = 'm',
|
||||
Hours = 'h',
|
||||
Days = 'd',
|
||||
DurationMs = 'dtdurationms',
|
||||
DurationS = 'dtdurations',
|
||||
DurationHms = 'dthms',
|
||||
DurationDhms = 'dtdhms',
|
||||
Timeticks = 'timeticks',
|
||||
ClockMs = 'clockms',
|
||||
ClockS = 'clocks',
|
||||
}
|
||||
|
||||
export enum ThroughputFormats {
|
||||
CountsPerSec = 'cps',
|
||||
OpsPerSec = 'ops',
|
||||
RequestsPerSec = 'reqps',
|
||||
ReadsPerSec = 'rps',
|
||||
WritesPerSec = 'wps',
|
||||
IOOpsPerSec = 'iops',
|
||||
CountsPerMin = 'cpm',
|
||||
OpsPerMin = 'opm',
|
||||
ReadsPerMin = 'rpm',
|
||||
WritesPerMin = 'wpm',
|
||||
}
|
||||
|
||||
export enum DataFormats {
|
||||
BytesIEC = 'bytes',
|
||||
BytesSI = 'decbytes',
|
||||
BitsIEC = 'bits',
|
||||
BitsSI = 'decbits',
|
||||
KibiBytes = 'kbytes',
|
||||
KiloBytes = 'deckbytes',
|
||||
MebiBytes = 'mbytes',
|
||||
MegaBytes = 'decmbytes',
|
||||
GibiBytes = 'gbytes',
|
||||
GigaBytes = 'decgbytes',
|
||||
TebiBytes = 'tbytes',
|
||||
TeraBytes = 'dectbytes',
|
||||
PebiBytes = 'pbytes',
|
||||
PetaBytes = 'decpbytes',
|
||||
}
|
||||
|
||||
export enum DataRateFormats {
|
||||
PacketsPerSec = 'pps',
|
||||
BytesPerSecIEC = 'binBps',
|
||||
BytesPerSecSI = 'Bps',
|
||||
BitsPerSecIEC = 'binbps',
|
||||
BitsPerSecSI = 'bps',
|
||||
KibiBytesPerSec = 'KiBs',
|
||||
KibiBitsPerSec = 'Kibits',
|
||||
KiloBytesPerSec = 'KBs',
|
||||
KiloBitsPerSec = 'Kbits',
|
||||
MebiBytesPerSec = 'MiBs',
|
||||
MebiBitsPerSec = 'Mibits',
|
||||
MegaBytesPerSec = 'MBs',
|
||||
MegaBitsPerSec = 'Mbits',
|
||||
GibiBytesPerSec = 'GiBs',
|
||||
GibiBitsPerSec = 'Gibits',
|
||||
GigaBytesPerSec = 'GBs',
|
||||
GigaBitsPerSec = 'Gbits',
|
||||
TebiBytesPerSec = 'TiBs',
|
||||
TebiBitsPerSec = 'Tibits',
|
||||
TeraBytesPerSec = 'TBs',
|
||||
TeraBitsPerSec = 'Tbits',
|
||||
PebiBytesPerSec = 'PiBs',
|
||||
PebiBitsPerSec = 'Pibits',
|
||||
PetaBytesPerSec = 'PBs',
|
||||
PetaBitsPerSec = 'Pbits',
|
||||
}
|
||||
|
||||
export enum HashRateFormats {
|
||||
HashesPerSec = 'Hs',
|
||||
KiloHashesPerSec = 'KHs',
|
||||
MegaHashesPerSec = 'MHs',
|
||||
GigaHashesPerSec = 'GHs',
|
||||
TeraHashesPerSec = 'THs',
|
||||
PetaHashesPerSec = 'PHs',
|
||||
ExaHashesPerSec = 'EHs',
|
||||
}
|
||||
|
||||
export enum MiscellaneousFormats {
|
||||
None = 'none',
|
||||
String = 'string',
|
||||
Short = 'short',
|
||||
Percent = 'percent',
|
||||
PercentUnit = 'percentunit',
|
||||
Humidity = 'humidity',
|
||||
Decibel = 'dB',
|
||||
Hexadecimal0x = 'hex0x',
|
||||
Hexadecimal = 'hex',
|
||||
ScientificNotation = 'sci',
|
||||
LocaleFormat = 'locale',
|
||||
Pixels = 'pixel',
|
||||
}
|
||||
|
||||
export enum AccelerationFormats {
|
||||
MetersPerSecondSquared = 'accMS2',
|
||||
FeetPerSecondSquared = 'accFS2',
|
||||
GUnit = 'accG',
|
||||
}
|
||||
|
||||
export enum AngularFormats {
|
||||
Degree = 'degree',
|
||||
Radian = 'radian',
|
||||
Gradian = 'grad',
|
||||
ArcMinute = 'arcmin',
|
||||
ArcSecond = 'arcsec',
|
||||
}
|
||||
|
||||
export enum AreaFormats {
|
||||
SquareMeters = 'areaM2',
|
||||
SquareFeet = 'areaF2',
|
||||
SquareMiles = 'areaMI2',
|
||||
}
|
||||
|
||||
export enum FlopsFormats {
|
||||
FLOPs = 'flops',
|
||||
MFLOPs = 'mflops',
|
||||
GFLOPs = 'gflops',
|
||||
TFLOPs = 'tflops',
|
||||
PFLOPs = 'pflops',
|
||||
EFLOPs = 'eflops',
|
||||
ZFLOPs = 'zflops',
|
||||
YFLOPs = 'yflops',
|
||||
}
|
||||
|
||||
export enum ConcentrationFormats {
|
||||
PPM = 'ppm',
|
||||
PPB = 'conppb',
|
||||
NgM3 = 'conngm3',
|
||||
NgNM3 = 'conngNm3',
|
||||
UgM3 = 'conμgm3',
|
||||
UgNM3 = 'conμgNm3',
|
||||
MgM3 = 'conmgm3',
|
||||
MgNM3 = 'conmgNm3',
|
||||
GM3 = 'congm3',
|
||||
GNM3 = 'congNm3',
|
||||
MgDL = 'conmgdL',
|
||||
MmolL = 'conmmolL',
|
||||
}
|
||||
|
||||
export enum CurrencyFormats {
|
||||
USD = 'currencyUSD',
|
||||
GBP = 'currencyGBP',
|
||||
EUR = 'currencyEUR',
|
||||
JPY = 'currencyJPY',
|
||||
RUB = 'currencyRUB',
|
||||
UAH = 'currencyUAH',
|
||||
BRL = 'currencyBRL',
|
||||
DKK = 'currencyDKK',
|
||||
ISK = 'currencyISK',
|
||||
NOK = 'currencyNOK',
|
||||
SEK = 'currencySEK',
|
||||
CZK = 'currencyCZK',
|
||||
CHF = 'currencyCHF',
|
||||
PLN = 'currencyPLN',
|
||||
BTC = 'currencyBTC',
|
||||
MBTC = 'currencymBTC',
|
||||
UBTC = 'currencyμBTC',
|
||||
ZAR = 'currencyZAR',
|
||||
INR = 'currencyINR',
|
||||
KRW = 'currencyKRW',
|
||||
IDR = 'currencyIDR',
|
||||
PHP = 'currencyPHP',
|
||||
VND = 'currencyVND',
|
||||
}
|
||||
|
||||
export enum DatetimeFormats {
|
||||
ISO = 'dateTimeAsIso',
|
||||
ISONoDateIfToday = 'dateTimeAsIsoNoDateIfToday',
|
||||
US = 'dateTimeAsUS',
|
||||
USNoDateIfToday = 'dateTimeAsUSNoDateIfToday',
|
||||
Local = 'dateTimeAsLocal',
|
||||
LocalNoDateIfToday = 'dateTimeAsLocalNoDateIfToday',
|
||||
System = 'dateTimeAsSystem',
|
||||
FromNow = 'dateTimeFromNow',
|
||||
}
|
||||
|
||||
export enum PowerElectricalFormats {
|
||||
WATT = 'watt',
|
||||
KWATT = 'kwatt',
|
||||
MEGWATT = 'megwatt',
|
||||
GWATT = 'gwatt',
|
||||
MWATT = 'mwatt',
|
||||
WM2 = 'Wm2',
|
||||
VOLTAMP = 'voltamp',
|
||||
KVOLTAMP = 'kvoltamp',
|
||||
VOLTAMPREACT = 'voltampreact',
|
||||
KVOLTAMPREACT = 'kvoltampreact',
|
||||
WATTH = 'watth',
|
||||
WATTHPERKG = 'watthperkg',
|
||||
KWATTH = 'kwatth',
|
||||
KWATTM = 'kwattm',
|
||||
AMPH = 'amph',
|
||||
KAMPH = 'kamph',
|
||||
MAMPH = 'mamph',
|
||||
JOULE = 'joule',
|
||||
EV = 'ev',
|
||||
AMP = 'amp',
|
||||
KAMP = 'kamp',
|
||||
MAMP = 'mamp',
|
||||
VOLT = 'volt',
|
||||
KVOLT = 'kvolt',
|
||||
MVOLT = 'mvolt',
|
||||
DBM = 'dBm',
|
||||
OHM = 'ohm',
|
||||
KOHM = 'kohm',
|
||||
MOHM = 'Mohm',
|
||||
FARAD = 'farad',
|
||||
µFARAD = 'µfarad',
|
||||
NFARAD = 'nfarad',
|
||||
PFARAD = 'pfarad',
|
||||
FFARAD = 'ffarad',
|
||||
HENRY = 'henry',
|
||||
MHENRY = 'mhenry',
|
||||
µHENRY = 'µhenry',
|
||||
LUMENS = 'lumens',
|
||||
}
|
||||
|
||||
export enum FlowFormats {
|
||||
FLOWGPM = 'flowgpm',
|
||||
FLOWCMS = 'flowcms',
|
||||
FLOWCFS = 'flowcfs',
|
||||
FLOWCFM = 'flowcfm',
|
||||
LITREH = 'litreh',
|
||||
FLOWLPM = 'flowlpm',
|
||||
FLOWMLPM = 'flowmlpm',
|
||||
LUX = 'lux',
|
||||
}
|
||||
|
||||
export enum ForceFormats {
|
||||
FORCENM = 'forceNm',
|
||||
FORCEKNM = 'forcekNm',
|
||||
FORCEN = 'forceN',
|
||||
FORCEKN = 'forcekN',
|
||||
}
|
||||
|
||||
export enum MassFormats {
|
||||
MASSMG = 'massmg',
|
||||
MASSG = 'massg',
|
||||
MASSLB = 'masslb',
|
||||
MASSKG = 'masskg',
|
||||
MASST = 'masst',
|
||||
}
|
||||
|
||||
export enum LengthFormats {
|
||||
LENGTHMM = 'lengthmm',
|
||||
LENGTHIN = 'lengthin',
|
||||
LENGTHFT = 'lengthft',
|
||||
LENGTHM = 'lengthm',
|
||||
LENGTHKM = 'lengthkm',
|
||||
LENGTHMI = 'lengthmi',
|
||||
}
|
||||
|
||||
export enum PressureFormats {
|
||||
PRESSUREMBAR = 'pressurembar',
|
||||
PRESSUREBAR = 'pressurebar',
|
||||
PRESSUREKBAR = 'pressurekbar',
|
||||
PRESSUREPA = 'pressurepa',
|
||||
PRESSUREHPA = 'pressurehpa',
|
||||
PRESSUREKPA = 'pressurekpa',
|
||||
PRESSUREHG = 'pressurehg',
|
||||
PRESSUREPSI = 'pressurepsi',
|
||||
}
|
||||
|
||||
export enum RadiationFormats {
|
||||
RADBQ = 'radbq',
|
||||
RADCI = 'radci',
|
||||
RADGY = 'radgy',
|
||||
RADRAD = 'radrad',
|
||||
RADSV = 'radsv',
|
||||
RADMSV = 'radmsv',
|
||||
RADUSV = 'radusv',
|
||||
RADREM = 'radrem',
|
||||
RADEXPCKG = 'radexpckg',
|
||||
RADR = 'radr',
|
||||
RADSVH = 'radsvh',
|
||||
RADMSVH = 'radmsvh',
|
||||
RADUSVH = 'radusvh',
|
||||
}
|
||||
|
||||
export enum RotationSpeedFormats {
|
||||
ROTRPM = 'rotrpm',
|
||||
ROTHZ = 'rothz',
|
||||
ROTRADS = 'rotrads',
|
||||
ROTDEGS = 'rotdegs',
|
||||
}
|
||||
|
||||
export enum TemperatureFormats {
|
||||
CELSIUS = 'celsius',
|
||||
FAHRENHEIT = 'fahrenheit',
|
||||
KELVIN = 'kelvin',
|
||||
}
|
||||
|
||||
export enum VelocityFormats {
|
||||
METERS_PER_SECOND = 'velocityms',
|
||||
KILOMETERS_PER_HOUR = 'velocitykmh',
|
||||
MILES_PER_HOUR = 'velocitymph',
|
||||
KNOT = 'velocityknot',
|
||||
}
|
||||
|
||||
export enum VolumeFormats {
|
||||
MILLILITRE = 'mlitre',
|
||||
LITRE = 'litre',
|
||||
CUBIC_METER = 'm3',
|
||||
NORMAL_CUBIC_METER = 'Nm3',
|
||||
CUBIC_DECIMETER = 'dm3',
|
||||
GALLONS = 'gallons',
|
||||
}
|
||||
|
||||
export enum BooleanFormats {
|
||||
TRUE_FALSE = 'bool',
|
||||
YES_NO = 'bool_yes_no',
|
||||
ON_OFF = 'bool_on_off',
|
||||
}
|
||||
|
||||
export type Format = {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type Category = {
|
||||
name: string;
|
||||
formats: Format[];
|
||||
};
|
||||
|
||||
export type DataTypeCategories = Category[];
|
@ -67,7 +67,9 @@ function NewWidget({ selectedGraph, saveSettingOfPanel }: Props): JSX.Element {
|
||||
|
||||
const selectedWidget = getWidget();
|
||||
|
||||
const [title, setTitle] = useState<string>(selectedWidget?.title || '');
|
||||
const [title, setTitle] = useState<string>(
|
||||
selectedWidget?.title?.toString() || '',
|
||||
);
|
||||
const [description, setDescription] = useState<string>(
|
||||
selectedWidget?.description || '',
|
||||
);
|
||||
|
@ -0,0 +1,48 @@
|
||||
import { Select, SelectProps, Space } from 'antd';
|
||||
import { getCategorySelectOptionByName } from 'container/NewWidget/RightContainer/alertFomatCategories';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
|
||||
import { categoryToSupport } from './config';
|
||||
import { DefaultLabel, selectStyles } from './styles';
|
||||
import { IBuilderUnitsFilterProps } from './types';
|
||||
import { filterOption } from './utils';
|
||||
|
||||
function BuilderUnitsFilter({
|
||||
onChange,
|
||||
}: IBuilderUnitsFilterProps): JSX.Element {
|
||||
const { currentQuery, handleOnUnitsChange } = useQueryBuilder();
|
||||
|
||||
const selectedValue = currentQuery?.unit;
|
||||
|
||||
const allOptions = categoryToSupport.map((category) => ({
|
||||
label: category,
|
||||
options: getCategorySelectOptionByName(category),
|
||||
}));
|
||||
|
||||
const onChangeHandler: SelectProps['onChange'] = (value): void => {
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
|
||||
handleOnUnitsChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<DefaultLabel>Y-axis unit</DefaultLabel>
|
||||
<Select
|
||||
style={selectStyles}
|
||||
onChange={onChangeHandler}
|
||||
value={selectedValue}
|
||||
options={allOptions}
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
placeholder="Select unit"
|
||||
filterOption={filterOption}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
export { BuilderUnitsFilter };
|
@ -0,0 +1,10 @@
|
||||
import { CategoryNames } from 'container/NewWidget/RightContainer/types';
|
||||
|
||||
export const categoryToSupport = [
|
||||
CategoryNames.Data,
|
||||
CategoryNames.DataRate,
|
||||
CategoryNames.Time,
|
||||
CategoryNames.Throughput,
|
||||
CategoryNames.Miscellaneous,
|
||||
CategoryNames.Boolean,
|
||||
];
|
@ -0,0 +1 @@
|
||||
export { BuilderUnitsFilter } from './BuilderUnits';
|
@ -0,0 +1,11 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const selectStyles: React.CSSProperties = {
|
||||
minWidth: '10rem',
|
||||
};
|
||||
|
||||
export const DefaultLabel = styled.label`
|
||||
display: inline-block;
|
||||
font-size: 1rem;
|
||||
line-height: 2rem;
|
||||
`;
|
@ -0,0 +1,3 @@
|
||||
export interface IBuilderUnitsFilterProps {
|
||||
onChange?: (value: string) => void;
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { DefaultOptionType } from 'antd/es/select';
|
||||
|
||||
export const filterOption = (
|
||||
inputValue: string,
|
||||
option: DefaultOptionType['options'][number],
|
||||
): boolean => option.label.toLowerCase().includes(inputValue.toLowerCase());
|
@ -125,7 +125,7 @@ function QueryBuilderSearch({
|
||||
|
||||
useEffect(() => {
|
||||
const initialTagFilters: TagFilter = { items: [], op: 'AND' };
|
||||
const initialSourceKeys = query.filters.items.map(
|
||||
const initialSourceKeys = query.filters.items?.map(
|
||||
(item) => item.key as BaseAutocompleteData,
|
||||
);
|
||||
initialTagFilters.items = tags.map((tag) => {
|
||||
|
@ -1,4 +1,5 @@
|
||||
export { AggregatorFilter } from './AggregatorFilter';
|
||||
export { BuilderUnitsFilter } from './BuilderUnitsFilter';
|
||||
export { GroupByFilter } from './GroupByFilter';
|
||||
export { HavingFilter } from './HavingFilter';
|
||||
export { OperatorsSelect } from './OperatorsSelect';
|
||||
|
12
frontend/src/hooks/apDex/useGetApDexSettings.ts
Normal file
12
frontend/src/hooks/apDex/useGetApDexSettings.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { getApDexSettings } from 'api/metrics/ApDex/getApDexSettings';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import { ApDexPayloadAndSettingsProps } from 'types/api/metrics/getApDex';
|
||||
|
||||
export const useGetApDexSettings = (
|
||||
servicename: string,
|
||||
): UseQueryResult<AxiosResponse<ApDexPayloadAndSettingsProps[]>, AxiosError> =>
|
||||
useQuery<AxiosResponse<ApDexPayloadAndSettingsProps[]>, AxiosError>({
|
||||
queryKey: [{ servicename }],
|
||||
queryFn: async () => getApDexSettings(servicename),
|
||||
});
|
12
frontend/src/hooks/apDex/useGetMetricMeta.ts
Normal file
12
frontend/src/hooks/apDex/useGetMetricMeta.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { getMetricMeta } from 'api/metrics/ApDex/getMetricMeta';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import { MetricMetaProps } from 'types/api/metrics/getApDex';
|
||||
|
||||
export const useGetMetricMeta = (
|
||||
metricName: string,
|
||||
): UseQueryResult<AxiosResponse<MetricMetaProps>, AxiosError> =>
|
||||
useQuery<AxiosResponse<MetricMetaProps>, AxiosError>({
|
||||
queryKey: [{ metricName }],
|
||||
queryFn: async () => getMetricMeta(metricName),
|
||||
});
|
21
frontend/src/hooks/apDex/useSetApDexSettings.ts
Normal file
21
frontend/src/hooks/apDex/useSetApDexSettings.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { setApDexSettings } from 'api/metrics/ApDex/apDexSettings';
|
||||
import { useMutation, UseMutationResult } from 'react-query';
|
||||
import {
|
||||
ApDexPayloadAndSettingsProps,
|
||||
SetApDexPayloadProps,
|
||||
} from 'types/api/metrics/getApDex';
|
||||
|
||||
export const useSetApDexSettings = ({
|
||||
servicename,
|
||||
threshold,
|
||||
excludeStatusCode,
|
||||
}: ApDexPayloadAndSettingsProps): UseMutationResult<
|
||||
SetApDexPayloadProps,
|
||||
Error,
|
||||
ApDexPayloadAndSettingsProps
|
||||
> =>
|
||||
useMutation<SetApDexPayloadProps, Error, ApDexPayloadAndSettingsProps>({
|
||||
mutationKey: [servicename, threshold.toString(), excludeStatusCode],
|
||||
mutationFn: async () =>
|
||||
setApDexSettings({ servicename, threshold, excludeStatusCode }),
|
||||
});
|
@ -1,20 +1,20 @@
|
||||
import { OPERATORS, QueryBuilderKeys } from 'constants/queryBuilder';
|
||||
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
|
||||
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { getGeneratedFilterQueryString } from 'lib/getGeneratedFilterQueryString';
|
||||
import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAutocompleteFromCustomValue';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import {
|
||||
BaseAutocompleteData,
|
||||
IQueryAutocompleteResponse,
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@ -31,6 +31,9 @@ export const useActiveLog = (): UseActiveLog => {
|
||||
const { pathname } = useLocation();
|
||||
const history = useHistory();
|
||||
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const isLogsPage = useMemo(() => pathname === ROUTES.LOGS, [pathname]);
|
||||
|
||||
@ -60,48 +63,64 @@ export const useActiveLog = (): UseActiveLog => {
|
||||
const onClearActiveLog = useCallback((): void => setActiveLog(null), []);
|
||||
|
||||
const onAddToQueryExplorer = useCallback(
|
||||
(fieldKey: string, fieldValue: string, operator: string): void => {
|
||||
const keysAutocomplete: BaseAutocompleteData[] =
|
||||
queryClient.getQueryData<SuccessResponse<IQueryAutocompleteResponse>>(
|
||||
[QueryBuilderKeys.GET_AGGREGATE_KEYS],
|
||||
{ exact: false },
|
||||
)?.payload.attributeKeys || [];
|
||||
async (
|
||||
fieldKey: string,
|
||||
fieldValue: string,
|
||||
operator: string,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const keysAutocompleteResponse = await queryClient.fetchQuery(
|
||||
[QueryBuilderKeys.GET_AGGREGATE_KEYS, fieldKey],
|
||||
async () =>
|
||||
getAggregateKeys({
|
||||
searchText: fieldKey,
|
||||
aggregateOperator: currentQuery.builder.queryData[0].aggregateOperator,
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute:
|
||||
currentQuery.builder.queryData[0].aggregateAttribute.key,
|
||||
}),
|
||||
);
|
||||
|
||||
const existAutocompleteKey = chooseAutocompleteFromCustomValue(
|
||||
keysAutocomplete,
|
||||
fieldKey,
|
||||
);
|
||||
const keysAutocomplete: BaseAutocompleteData[] =
|
||||
keysAutocompleteResponse.payload?.attributeKeys || [];
|
||||
|
||||
const currentOperator =
|
||||
Object.keys(OPERATORS).find((op) => op === operator) || '';
|
||||
const existAutocompleteKey = chooseAutocompleteFromCustomValue(
|
||||
keysAutocomplete,
|
||||
fieldKey,
|
||||
);
|
||||
|
||||
const nextQuery: Query = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((item) => ({
|
||||
...item,
|
||||
filters: {
|
||||
...item.filters,
|
||||
items: [
|
||||
...item.filters.items.filter(
|
||||
(item) => item.key?.id !== existAutocompleteKey.id,
|
||||
),
|
||||
{
|
||||
id: uuid(),
|
||||
key: existAutocompleteKey,
|
||||
op: currentOperator,
|
||||
value: fieldValue,
|
||||
},
|
||||
],
|
||||
},
|
||||
})),
|
||||
},
|
||||
};
|
||||
const currentOperator = getOperatorValue(operator);
|
||||
|
||||
redirectWithQueryBuilderData(nextQuery);
|
||||
const nextQuery: Query = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((item) => ({
|
||||
...item,
|
||||
filters: {
|
||||
...item.filters,
|
||||
items: [
|
||||
...item.filters.items.filter(
|
||||
(item) => item.key?.id !== existAutocompleteKey.id,
|
||||
),
|
||||
{
|
||||
id: uuid(),
|
||||
key: existAutocompleteKey,
|
||||
op: currentOperator,
|
||||
value: fieldValue,
|
||||
},
|
||||
],
|
||||
},
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
redirectWithQueryBuilderData(nextQuery);
|
||||
} catch {
|
||||
notifications.error({ message: t('something_went_wrong') });
|
||||
}
|
||||
},
|
||||
[currentQuery, queryClient, redirectWithQueryBuilderData],
|
||||
[currentQuery, notifications, queryClient, redirectWithQueryBuilderData, t],
|
||||
);
|
||||
|
||||
const onAddToQueryLogs = useCallback(
|
||||
|
13
frontend/src/hooks/useFeatureFlag/utils.test.ts
Normal file
13
frontend/src/hooks/useFeatureFlag/utils.test.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
|
||||
import { isFeatureKeys } from './utils';
|
||||
|
||||
describe('Feature Keys', () => {
|
||||
it('should return true for a valid feature key', () => {
|
||||
expect(isFeatureKeys(FeatureKeys.ALERT_CHANNEL_MSTEAMS)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for an invalid feature key', () => {
|
||||
expect(isFeatureKeys('invalid')).toBe(false);
|
||||
});
|
||||
});
|
4
frontend/src/hooks/useFeatureFlag/utils.ts
Normal file
4
frontend/src/hooks/useFeatureFlag/utils.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
|
||||
export const isFeatureKeys = (key: string): key is keyof typeof FeatureKeys =>
|
||||
Object.keys(FeatureKeys).includes(key);
|
@ -9,8 +9,9 @@ export const chooseAutocompleteFromCustomValue = (
|
||||
(sourceAutoComplete) => value === sourceAutoComplete.key,
|
||||
);
|
||||
|
||||
if (!firstBaseAutoCompleteValue)
|
||||
return { ...initialAutocompleteData, key: value };
|
||||
if (!firstBaseAutoCompleteValue) {
|
||||
return { ...initialAutocompleteData, key: value, dataType: 'string' };
|
||||
}
|
||||
|
||||
return firstBaseAutoCompleteValue;
|
||||
};
|
||||
|
@ -33,5 +33,6 @@ export const mapQueryDataFromApi = (
|
||||
clickhouse_sql: clickhouseSql,
|
||||
queryType: compositeQuery.queryType,
|
||||
id: uuid(),
|
||||
unit: compositeQuery.unit,
|
||||
};
|
||||
};
|
||||
|
@ -2,6 +2,8 @@ import { Typography } from 'antd';
|
||||
import get from 'api/channels/get';
|
||||
import Spinner from 'components/Spinner';
|
||||
import {
|
||||
MsTeamsChannel,
|
||||
MsTeamsType,
|
||||
PagerChannel,
|
||||
PagerType,
|
||||
SlackChannel,
|
||||
@ -39,9 +41,11 @@ function ChannelsEdit(): JSX.Element {
|
||||
|
||||
const prepChannelConfig = (): {
|
||||
type: string;
|
||||
channel: SlackChannel & WebhookChannel & PagerChannel;
|
||||
channel: SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel;
|
||||
} => {
|
||||
let channel: SlackChannel & WebhookChannel & PagerChannel = { name: '' };
|
||||
let channel: SlackChannel & WebhookChannel & PagerChannel & MsTeamsChannel = {
|
||||
name: '',
|
||||
};
|
||||
if (value && 'slack_configs' in value) {
|
||||
const slackConfig = value.slack_configs[0];
|
||||
channel = slackConfig;
|
||||
@ -50,6 +54,15 @@ function ChannelsEdit(): JSX.Element {
|
||||
channel,
|
||||
};
|
||||
}
|
||||
|
||||
if (value && 'msteams_configs' in value) {
|
||||
const msteamsConfig = value.msteams_configs[0];
|
||||
channel = msteamsConfig;
|
||||
return {
|
||||
type: MsTeamsType,
|
||||
channel,
|
||||
};
|
||||
}
|
||||
if (value && 'pagerduty_configs' in value) {
|
||||
const pagerConfig = value.pagerduty_configs[0];
|
||||
channel = pagerConfig;
|
||||
|
@ -0,0 +1,65 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import { APPLICATION_SETTINGS } from '../constants';
|
||||
import { thresholdMockData } from './__mock__/thresholdMockData';
|
||||
import ApDexApplication from './ApDexApplication';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useParams: (): {
|
||||
servicename: string;
|
||||
} => ({ servicename: 'mockServiceName' }),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/apDex/useGetApDexSettings', () => ({
|
||||
__esModule: true,
|
||||
useGetApDexSettings: jest.fn().mockReturnValue({
|
||||
data: thresholdMockData,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/apDex/useSetApDexSettings', () => ({
|
||||
__esModule: true,
|
||||
useSetApDexSettings: jest.fn().mockReturnValue({
|
||||
mutateAsync: jest.fn(),
|
||||
isLoading: false,
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('ApDexApplication', () => {
|
||||
it('should render the component', () => {
|
||||
render(<ApDexApplication />);
|
||||
|
||||
expect(screen.getByText('Settings')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should open the popover when the settings button is clicked', async () => {
|
||||
render(<ApDexApplication />);
|
||||
|
||||
const button = screen.getByText('Settings');
|
||||
fireEvent.click(button);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(APPLICATION_SETTINGS)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should close the popover when the close button is clicked', async () => {
|
||||
render(<ApDexApplication />);
|
||||
|
||||
const button = screen.getByText('Settings');
|
||||
fireEvent.click(button);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(APPLICATION_SETTINGS)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const closeButton = screen.getByText('Cancel');
|
||||
fireEvent.click(closeButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(APPLICATION_SETTINGS)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,56 @@
|
||||
import { SettingOutlined } from '@ant-design/icons';
|
||||
import { Popover } from 'antd';
|
||||
import { IServiceName } from 'container/MetricsApplication/Tabs/types';
|
||||
import { useGetApDexSettings } from 'hooks/apDex/useGetApDexSettings';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
import { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { Button } from '../styles';
|
||||
import ApDexSettings from './ApDexSettings';
|
||||
|
||||
function ApDexApplication(): JSX.Element {
|
||||
const { servicename } = useParams<IServiceName>();
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
refetch: refetchGetApDexSetting,
|
||||
} = useGetApDexSettings(servicename);
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
useErrorNotification(error);
|
||||
|
||||
const handlePopOverClose = (): void => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleOpenChange = (newOpen: boolean): void => {
|
||||
setIsOpen(newOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
placement="bottomRight"
|
||||
destroyTooltipOnHide
|
||||
trigger={['click']}
|
||||
showArrow={false}
|
||||
open={isOpen}
|
||||
onOpenChange={handleOpenChange}
|
||||
content={
|
||||
<ApDexSettings
|
||||
servicename={servicename}
|
||||
handlePopOverClose={handlePopOverClose}
|
||||
isLoading={isLoading}
|
||||
data={data}
|
||||
refetchGetApDexSetting={refetchGetApDexSetting}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button size="middle" icon={<SettingOutlined />}>
|
||||
Settings
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default ApDexApplication;
|
@ -0,0 +1,62 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import { axiosResponseThresholdData } from './__mock__/axiosResponseMockThresholdData';
|
||||
import ApDexSettings from './ApDexSettings';
|
||||
|
||||
jest.mock('hooks/apDex/useSetApDexSettings', () => ({
|
||||
__esModule: true,
|
||||
useSetApDexSettings: jest.fn().mockReturnValue({
|
||||
mutateAsync: jest.fn(),
|
||||
isLoading: false,
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('ApDexSettings', () => {
|
||||
it('should render the component', () => {
|
||||
render(
|
||||
<ApDexSettings
|
||||
servicename="mockServiceName"
|
||||
handlePopOverClose={jest.fn()}
|
||||
isLoading={false}
|
||||
data={axiosResponseThresholdData}
|
||||
refetchGetApDexSetting={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Application Settings')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the spinner when the data is loading', () => {
|
||||
render(
|
||||
<ApDexSettings
|
||||
servicename="mockServiceName"
|
||||
handlePopOverClose={jest.fn()}
|
||||
isLoading
|
||||
data={axiosResponseThresholdData}
|
||||
refetchGetApDexSetting={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should close the popover when the cancel button is clicked', async () => {
|
||||
const mockHandlePopOverClose = jest.fn();
|
||||
render(
|
||||
<ApDexSettings
|
||||
servicename="mockServiceName"
|
||||
handlePopOverClose={mockHandlePopOverClose}
|
||||
isLoading={false}
|
||||
data={axiosResponseThresholdData}
|
||||
refetchGetApDexSetting={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const button = screen.getByText('Cancel');
|
||||
fireEvent.click(button);
|
||||
await waitFor(() => {
|
||||
expect(mockHandlePopOverClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
119
frontend/src/pages/MetricsApplication/ApDex/ApDexSettings.tsx
Normal file
119
frontend/src/pages/MetricsApplication/ApDex/ApDexSettings.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { Card, InputNumber } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import {
|
||||
apDexToolTipText,
|
||||
apDexToolTipUrl,
|
||||
apDexToolTipUrlText,
|
||||
} from 'constants/apDex';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { useSetApDexSettings } from 'hooks/apDex/useSetApDexSettings';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { APPLICATION_SETTINGS } from '../constants';
|
||||
import {
|
||||
AppDexThresholdContainer,
|
||||
Button,
|
||||
SaveAndCancelContainer,
|
||||
SaveButton,
|
||||
Typography,
|
||||
} from '../styles';
|
||||
import { onSaveApDexSettings } from '../utils';
|
||||
import { ApDexSettingsProps } from './types';
|
||||
|
||||
function ApDexSettings({
|
||||
servicename,
|
||||
handlePopOverClose,
|
||||
isLoading,
|
||||
data,
|
||||
refetchGetApDexSetting,
|
||||
}: ApDexSettingsProps): JSX.Element {
|
||||
const [thresholdValue, setThresholdValue] = useState(() => {
|
||||
if (data) {
|
||||
return data.data[0].threshold;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { isLoading: isApDexLoading, mutateAsync } = useSetApDexSettings({
|
||||
servicename,
|
||||
threshold: thresholdValue,
|
||||
excludeStatusCode: '',
|
||||
});
|
||||
|
||||
const handleThreadholdChange = (value: number | null): void => {
|
||||
if (value !== null) {
|
||||
setThresholdValue(value);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Typography.Text style={{ color: themeColors.white }}>
|
||||
<Spinner height="5vh" tip="Loading..." />
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={APPLICATION_SETTINGS}
|
||||
extra={<CloseOutlined width={10} height={10} onClick={handlePopOverClose} />}
|
||||
actions={[
|
||||
<SaveAndCancelContainer key="SaveAndCancelContainer">
|
||||
<Button onClick={handlePopOverClose}>Cancel</Button>
|
||||
<SaveButton
|
||||
onClick={onSaveApDexSettings({
|
||||
handlePopOverClose,
|
||||
mutateAsync,
|
||||
notifications,
|
||||
refetchGetApDexSetting,
|
||||
servicename,
|
||||
thresholdValue,
|
||||
})}
|
||||
type="primary"
|
||||
loading={isApDexLoading}
|
||||
>
|
||||
Save
|
||||
</SaveButton>
|
||||
</SaveAndCancelContainer>,
|
||||
]}
|
||||
>
|
||||
<AppDexThresholdContainer>
|
||||
<Typography>
|
||||
Apdex threshold (in seconds){' '}
|
||||
<TextToolTip
|
||||
text={apDexToolTipText}
|
||||
url={apDexToolTipUrl}
|
||||
useFilledIcon={false}
|
||||
urlText={apDexToolTipUrlText}
|
||||
/>
|
||||
</Typography>
|
||||
<InputNumber
|
||||
value={thresholdValue}
|
||||
onChange={handleThreadholdChange}
|
||||
min={0}
|
||||
step={0.1}
|
||||
/>
|
||||
</AppDexThresholdContainer>
|
||||
{/* TODO: Add this feature later when backend is ready to support it. */}
|
||||
{/* <ExcludeErrorCodeContainer>
|
||||
<Typography.Text>
|
||||
Exclude following error codes from error rate calculation
|
||||
</Typography.Text>
|
||||
<Input placeholder="e.g. 406, 418" />
|
||||
</ExcludeErrorCodeContainer> */}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
ApDexSettings.defaultProps = {
|
||||
isLoading: undefined,
|
||||
data: undefined,
|
||||
refetchGetApDexSetting: undefined,
|
||||
};
|
||||
|
||||
export default ApDexSettings;
|
@ -0,0 +1,9 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
|
||||
export const axiosResponseThresholdData = {
|
||||
data: [
|
||||
{
|
||||
threshold: 0.5,
|
||||
},
|
||||
],
|
||||
} as AxiosResponse;
|
@ -0,0 +1,7 @@
|
||||
export const thresholdMockData = {
|
||||
data: [
|
||||
{
|
||||
threshold: 0.5,
|
||||
},
|
||||
],
|
||||
};
|
10
frontend/src/pages/MetricsApplication/ApDex/types.ts
Normal file
10
frontend/src/pages/MetricsApplication/ApDex/types.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { ApDexPayloadAndSettingsProps } from 'types/api/metrics/getApDex';
|
||||
|
||||
export interface ApDexSettingsProps {
|
||||
servicename: string;
|
||||
handlePopOverClose: () => void;
|
||||
isLoading?: boolean;
|
||||
data?: AxiosResponse<ApDexPayloadAndSettingsProps[]>;
|
||||
refetchGetApDexSetting?: () => void;
|
||||
}
|
1
frontend/src/pages/MetricsApplication/constants.ts
Normal file
1
frontend/src/pages/MetricsApplication/constants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const APPLICATION_SETTINGS = 'Application Settings';
|
@ -8,6 +8,7 @@ import history from 'lib/history';
|
||||
import { useMemo } from 'react';
|
||||
import { generatePath, useParams } from 'react-router-dom';
|
||||
|
||||
import ApDexApplication from './ApDex/ApDexApplication';
|
||||
import { MetricsApplicationTab, TAB_KEY_VS_LABEL } from './types';
|
||||
import useMetricsApplicationTabKey from './useMetricsApplicationTabKey';
|
||||
|
||||
@ -49,6 +50,7 @@ function MetricsApplication(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<ResourceAttributesFilter />
|
||||
<ApDexApplication />
|
||||
<RouteTab routes={routes} history={history} activeKey={activeKey} />
|
||||
</>
|
||||
);
|
||||
|
44
frontend/src/pages/MetricsApplication/styles.ts
Normal file
44
frontend/src/pages/MetricsApplication/styles.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import {
|
||||
Button as ButtonComponent,
|
||||
Typography as TypographyComponent,
|
||||
} from 'antd';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Button = styled(ButtonComponent)`
|
||||
&&& {
|
||||
width: min-content;
|
||||
align-self: flex-end;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AppDexThresholdContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const Typography = styled(TypographyComponent)`
|
||||
&&& {
|
||||
width: 24rem;
|
||||
margin: 0.5rem 0;
|
||||
color: ${themeColors.white};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SaveAndCancelContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-right: 1rem;
|
||||
`;
|
||||
|
||||
export const SaveButton = styled(ButtonComponent)`
|
||||
&&& {
|
||||
margin: 0 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ExcludeErrorCodeContainer = styled.div`
|
||||
margin: 1rem 0;
|
||||
`;
|
@ -1,3 +1,10 @@
|
||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import { UseMutateAsyncFunction } from 'react-query';
|
||||
import {
|
||||
ApDexPayloadAndSettingsProps,
|
||||
SetApDexPayloadProps,
|
||||
} from 'types/api/metrics/getApDex';
|
||||
|
||||
export enum MetricsApplicationTab {
|
||||
OVER_METRICS = 'OVER_METRICS',
|
||||
DB_CALL_METRICS = 'DB_CALL_METRICS',
|
||||
@ -9,3 +16,16 @@ export const TAB_KEY_VS_LABEL = {
|
||||
[MetricsApplicationTab.DB_CALL_METRICS]: 'DB Call Metrics',
|
||||
[MetricsApplicationTab.EXTERNAL_METRICS]: 'External Metrics',
|
||||
};
|
||||
|
||||
export interface OnSaveApDexSettingsProps {
|
||||
thresholdValue: number;
|
||||
servicename: string;
|
||||
notifications: NotificationInstance;
|
||||
refetchGetApDexSetting?: VoidFunction;
|
||||
mutateAsync: UseMutateAsyncFunction<
|
||||
SetApDexPayloadProps,
|
||||
Error,
|
||||
ApDexPayloadAndSettingsProps
|
||||
>;
|
||||
handlePopOverClose: VoidFunction;
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
|
||||
import { TAB_KEYS_VS_METRICS_APPLICATION_KEY } from './config';
|
||||
import { MetricsApplicationTab } from './types';
|
||||
import { MetricsApplicationTab, OnSaveApDexSettingsProps } from './types';
|
||||
|
||||
export const isMetricsApplicationTab = (
|
||||
tab: string,
|
||||
@ -15,3 +18,29 @@ export const getMetricsApplicationKey = (
|
||||
|
||||
return MetricsApplicationTab.OVER_METRICS;
|
||||
};
|
||||
|
||||
export const onSaveApDexSettings = ({
|
||||
thresholdValue,
|
||||
refetchGetApDexSetting,
|
||||
mutateAsync,
|
||||
notifications,
|
||||
handlePopOverClose,
|
||||
servicename,
|
||||
}: OnSaveApDexSettingsProps) => async (): Promise<void> => {
|
||||
if (!refetchGetApDexSetting) return;
|
||||
|
||||
try {
|
||||
await mutateAsync({
|
||||
servicename,
|
||||
threshold: thresholdValue,
|
||||
excludeStatusCode: '',
|
||||
});
|
||||
await refetchGetApDexSetting();
|
||||
} catch (err) {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG,
|
||||
});
|
||||
} finally {
|
||||
handlePopOverClose();
|
||||
}
|
||||
};
|
||||
|
@ -71,6 +71,7 @@ export const QueryBuilderContext = createContext<QueryBuilderContextType>({
|
||||
updateAllQueriesOperators: () => initialQueriesMap.metrics,
|
||||
updateQueriesData: () => initialQueriesMap.metrics,
|
||||
initQueryBuilderData: () => {},
|
||||
handleOnUnitsChange: () => {},
|
||||
});
|
||||
|
||||
export function QueryBuilderProvider({
|
||||
@ -176,6 +177,7 @@ export function QueryBuilderProvider({
|
||||
queryData: setupedQueryData,
|
||||
},
|
||||
id: query.id,
|
||||
unit: query.unit,
|
||||
};
|
||||
|
||||
const nextQuery: Query = {
|
||||
@ -474,6 +476,7 @@ export function QueryBuilderProvider({
|
||||
promql,
|
||||
clickhouse_sql: clickhouseSql,
|
||||
id: uuid(),
|
||||
unit: query.unit || initialQueryState.unit,
|
||||
};
|
||||
|
||||
urlQuery.set(
|
||||
@ -513,6 +516,7 @@ export function QueryBuilderProvider({
|
||||
promql: currentQuery.promql,
|
||||
id: currentQuery.id,
|
||||
queryType,
|
||||
unit: currentQuery.unit,
|
||||
},
|
||||
maxTime,
|
||||
minTime,
|
||||
@ -550,6 +554,16 @@ export function QueryBuilderProvider({
|
||||
stagedQuery,
|
||||
]);
|
||||
|
||||
const handleOnUnitsChange = useCallback(
|
||||
(unit: string) => {
|
||||
setCurrentQuery((prevState) => ({
|
||||
...prevState,
|
||||
unit,
|
||||
}));
|
||||
},
|
||||
[setCurrentQuery],
|
||||
);
|
||||
|
||||
const query: Query = useMemo(
|
||||
() => ({
|
||||
...currentQuery,
|
||||
@ -585,6 +599,7 @@ export function QueryBuilderProvider({
|
||||
updateAllQueriesOperators,
|
||||
updateQueriesData,
|
||||
initQueryBuilderData,
|
||||
handleOnUnitsChange,
|
||||
}),
|
||||
[
|
||||
query,
|
||||
@ -607,6 +622,7 @@ export function QueryBuilderProvider({
|
||||
updateAllQueriesOperators,
|
||||
updateQueriesData,
|
||||
initQueryBuilderData,
|
||||
handleOnUnitsChange,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -34,6 +34,7 @@ export async function GetMetricQueryRange({
|
||||
compositeQuery: {
|
||||
queryType: query.queryType,
|
||||
panelType: graphType,
|
||||
unit: query?.unit,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
BuilderClickHouseResource,
|
||||
BuilderPromQLResource,
|
||||
BuilderQueryDataResourse,
|
||||
Query,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
@ -12,4 +13,5 @@ export interface ICompositeMetricQuery {
|
||||
chQueries: BuilderClickHouseResource;
|
||||
queryType: EQueryType;
|
||||
panelType: PANEL_TYPES;
|
||||
unit: Query['unit'];
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ export interface RuleCondition {
|
||||
op?: string | undefined;
|
||||
target?: number | undefined;
|
||||
matchType?: string | undefined;
|
||||
targetUnit?: string | undefined;
|
||||
}
|
||||
|
||||
export interface Labels {
|
||||
|
8
frontend/src/types/api/channels/createMsTeams.ts
Normal file
8
frontend/src/types/api/channels/createMsTeams.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { MsTeamsChannel } from 'container/CreateAlertChannels/config';
|
||||
|
||||
export type Props = MsTeamsChannel;
|
||||
|
||||
export interface PayloadProps {
|
||||
data: string;
|
||||
status: string;
|
||||
}
|
10
frontend/src/types/api/channels/editMsTeams.ts
Normal file
10
frontend/src/types/api/channels/editMsTeams.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { MsTeamsChannel } from 'container/CreateAlertChannels/config';
|
||||
|
||||
export interface Props extends MsTeamsChannel {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
data: string;
|
||||
status: string;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import { ReactNode } from 'react';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
@ -58,7 +59,7 @@ export interface IBaseWidget {
|
||||
isStacked: boolean;
|
||||
id: string;
|
||||
panelTypes: PANEL_TYPES;
|
||||
title: string;
|
||||
title: ReactNode;
|
||||
description: string;
|
||||
opacity: string;
|
||||
nullZeroValues: string;
|
||||
|
14
frontend/src/types/api/metrics/getApDex.ts
Normal file
14
frontend/src/types/api/metrics/getApDex.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export interface ApDexPayloadAndSettingsProps {
|
||||
servicename: string;
|
||||
threshold: number;
|
||||
excludeStatusCode: string;
|
||||
}
|
||||
|
||||
export interface SetApDexPayloadProps {
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface MetricMetaProps {
|
||||
delta: boolean;
|
||||
le: number[];
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user