mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 16:10:43 +00:00
feat: prebuilt template protection, per-destination templates, and correlation settings fixes (#12423)
## Summary Combines #12414 (prebuilt template protection) and #12421 (correlation settings + AI i18n + traces fixes). ### Prebuilt template protection + per-destination template selection - Lets users attach a custom template to a prebuilt destination so multiple destinations of the same type (e.g. three Slack channels) can deliver different message bodies. - Protects system-managed \`prebuilt_<type>\` templates from accidental deletion (403) while still allowing edits; create with a reserved name blocked (400). - Adds Prebuilt/Custom badges, All/Prebuilt/Custom toggle on both Templates and Destinations lists. - Per-row Clone action on templates (\`Copy_of_<name>\` prefilled); Template column on destinations list. - Cross-type prebuilt template mismatches blocked in the dropdown (e.g. \`prebuilt_servicenow\` cannot be selected for a Slack destination). - Select-All fix in the shared OTable composable — \`toggleAllRows\`/\`isAllSelected\`/\`isIndeterminate\` now respect \`isRowSelectable\`. Design at: https://github.com/openobserve/designs/blob/main/alerts/prebuilt-template-protection/design.md ### Correlation settings visibility + AI i18n + traces tab fixes - **Correlation Settings tab** always visible for enterprise: removed unnecessary \`service_streams_enabled\` guard — route was always registered, only the nav item was incorrectly gated. - **Cross-alert fingerprint groups** hidden when org-level dedup is disabled; save payload sanitises \`alert_fingerprint_groups\` to \`[]\` when cross-alert dedup is off. - **AI suggestion cards** use i18n prompt keys (\`t('aiAssistant.capabilities.{id}.prompt')\`) instead of hardcoded English — prompt keys added to all 12 locale files. - **LLM Insights / Sessions tabs** always shown when \`showLLMUI\` is enabled; org-switch re-mounts both components via \`:key\`. - **LLMInsightsDashboard** layout, background, and empty-state alignment fixed. Fixes openobserve/o2-enterprise#1850, openobserve/o2-enterprise#1851, openobserve/o2-enterprise#1852 ## Changes | Layer | File | What | |---|---|---| | Backend | \`src/service/alerts/templates.rs\` | \`is_prebuilt_template_name()\` helper; block create with reserved name; block delete of prebuilt; edit allowed. | | Backend | \`src/service/db/alerts/templates.rs\` | New error variants: \`PrebuiltReadOnly\` (403), \`ReservedName\` (400). | | Backend | \`src/handler/http/request/alerts/templates.rs\` | HTTP error mapping + two unit tests. | | Backend | \`src/handler/http/models/destinations.rs\` | Public Template DTO carries \`isPrebuilt: bool\`. | | Frontend | \`web/src/components/alerts/AddDestination.vue\` | Template dropdown for prebuilt destinations with sentinel-backed Default option; edit-mode remap; i18n for all label strings. | | Frontend | \`web/src/composables/usePrebuiltDestinations.ts\` | \`templateOverride\` param on create/update; empty → backend default. | | Frontend | \`web/src/components/alerts/TemplateList.vue\` | Prebuilt/Custom badges, Clone action, delete-blocked for prebuilts, All/Prebuilt/Custom toggle, prebuilts excluded from Select All. | | Frontend | \`web/src/components/alerts/AddTemplate.vue\` | \`isClone\` prop — pre-fill without entering update mode. | | Frontend | \`web/src/components/alerts/AlertsDestinationList.vue\` | New Template column (ellipsis + tooltip, Default badge), All/Prebuilt/Custom toggle. | | Frontend | \`web/src/lib/core/Table/composables/useTableSelection.ts\` + \`OTable.vue\` | \`selectableRows()\` helper; Select All respects \`isRowSelectable\`; getter pattern restored for reactivity. | | Frontend | \`web/src/ts/interfaces/alert.ts\` | \`Template.isPrebuilt?: boolean\`. | | Frontend | \`web/src/components/alerts/OrganizationDeduplicationSettings.vue\` | Hide fingerprint groups when org dedup disabled; sanitise payload. | | Frontend | \`web/src/components/settings/index.vue\` | Remove \`service_streams_enabled\` guard from Correlation Settings nav item. | | Frontend | \`web/src/plugins/traces/Index.vue\` | Remove \`hasLLMStreams\` guard from LLM Insights / Sessions tabs. | | Frontend | \`web/src/plugins/traces/LLMInsightsDashboard.vue\` + \`SessionsList.vue\` | \`:key\` on org identifier for re-mount on org switch; layout fixes. | | Frontend | \`web/src/plugins/traces/SearchBar.vue\` | AI suggestion cards use i18n prompt keys. | | Frontend | \`web/src/components/ai-assistant/welcome/\` | Capability card prompts via i18n; all 12 locale files updated. | | Frontend | \`web/src/locales/languages/en.json\` (+ 10 other locales) | New keys: \`filter{All,Prebuilt,Custom}\`, \`templateDefaultBadge\`, \`prebuiltBadge\`, \`prebuiltBadgeHint\`, \`customBadge\`, \`clone\`, \`cloneTitle\`, \`systemReadOnly\`, \`templateDefaultOption\`, \`templateHelp\`; toast key moved to \`alert_destinations.saved\`. | ## Compatibility - **No schema migration.** "Is this a system template" is derived from the name matching \`prebuilt_<canonical_type>\` where canonical types are the eight registered in \`config/prebuilt-destinations.json\`. - **No ENT changes needed.** Verified the enterprise tree does not reference \`TemplateError\`, the public Template DTO, or the new helper. - **API additive:** \`GET /alerts/templates\` response gains \`isPrebuilt: bool\`. - **API breaking (narrow):** - \`POST /alerts/templates\` rejects the eight canonical reserved names with 400. - \`DELETE /alerts/templates/{name}\` rejects the eight canonical names with 403. ## Test plan - [x] \`cargo test --lib -p openobserve -- prebuilt_read_only reserved_name\` — both pass - [x] \`cargo check --bin openobserve\` — clean - [x] \`vue-tsc --noEmit\` — exit 0 - [ ] Create 3 Slack destinations — each can be assigned a different custom template - [ ] Template dropdown for a Slack destination shows only custom templates + Default sentinel (no \`prebuilt_servicenow\` etc.) - [ ] Attempting to delete a \`prebuilt_\` template returns a 403 toast - [ ] Attempting to create a template named \`prebuilt_slack\` returns a 400 error - [ ] Clone button pre-fills \`Copy_of_<name>\` and saves as a new template - [ ] Select All on a mixed prebuilt/custom template page: prebuilt rows unchecked, custom rows toggled - [ ] Enterprise build: Correlation Settings visible in sidebar without \`service_streams_enabled\` - [ ] Correlation Settings: disable org dedup → fingerprint groups section disappears; save → payload has empty \`alert_fingerprint_groups\` - [ ] Switch UI language → AI home suggestion cards send prompt in that language - [ ] Org with no LLM streams: LLM Insights + Sessions tabs still visible with correct empty state - [ ] Switch orgs on LLM Insights → data reloads for new org 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
github-actions[bot]
parent
ad9fef2242
commit
4ca42e29af
@@ -184,10 +184,15 @@ impl From<meta_dest::Template> for Template {
|
||||
meta_dest::TemplateType::Sns => (String::new(), DestinationType::Sns),
|
||||
};
|
||||
|
||||
// Shared rule lives in `config::prebuilt_loader` so the HTTP DTO and
|
||||
// the service-layer guards can't drift apart.
|
||||
let is_prebuilt = config::prebuilt_loader::is_prebuilt_template_name(&value.name);
|
||||
|
||||
Self {
|
||||
name: value.name,
|
||||
body: value.body,
|
||||
is_default: value.is_default.then_some(true),
|
||||
is_prebuilt,
|
||||
template_type,
|
||||
title,
|
||||
}
|
||||
@@ -341,6 +346,12 @@ pub struct Template {
|
||||
#[serde(rename = "isDefault")]
|
||||
#[serde(default)]
|
||||
pub is_default: Option<bool>,
|
||||
/// True for system-managed prebuilt templates (those whose name starts
|
||||
/// with `prebuilt_`). The public API refuses updates and deletes for
|
||||
/// these, so the UI should render them as read-only.
|
||||
#[serde(rename = "isPrebuilt")]
|
||||
#[serde(default)]
|
||||
pub is_prebuilt: bool,
|
||||
/// Indicates whether the body is
|
||||
/// http or email body
|
||||
#[serde(rename = "type")]
|
||||
@@ -456,6 +467,7 @@ mod tests {
|
||||
name: "tmpl".to_string(),
|
||||
body: "body".to_string(),
|
||||
is_default: None,
|
||||
is_prebuilt: false,
|
||||
template_type: DestinationType::Http,
|
||||
title: String::new(),
|
||||
};
|
||||
@@ -472,6 +484,7 @@ mod tests {
|
||||
name: "email_tmpl".to_string(),
|
||||
body: "hi".to_string(),
|
||||
is_default: Some(true),
|
||||
is_prebuilt: false,
|
||||
template_type: DestinationType::Email,
|
||||
title: "My Alert".to_string(),
|
||||
};
|
||||
@@ -489,6 +502,7 @@ mod tests {
|
||||
name: "s".to_string(),
|
||||
body: "b".to_string(),
|
||||
is_default: None,
|
||||
is_prebuilt: false,
|
||||
template_type: DestinationType::Sns,
|
||||
title: String::new(),
|
||||
};
|
||||
|
||||
@@ -37,6 +37,9 @@ impl From<TemplateError> for Response {
|
||||
TemplateError::DeleteWithDestination(e) => {
|
||||
MetaHttpResponse::conflict(TemplateError::DeleteWithDestination(e))
|
||||
}
|
||||
TemplateError::PrebuiltReadOnly(name) => {
|
||||
MetaHttpResponse::forbidden(TemplateError::PrebuiltReadOnly(name))
|
||||
}
|
||||
other_err => MetaHttpResponse::bad_request(other_err),
|
||||
}
|
||||
}
|
||||
@@ -458,4 +461,22 @@ mod tests {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prebuilt_read_only_is_forbidden() {
|
||||
assert_eq!(
|
||||
status(TemplateError::PrebuiltReadOnly(
|
||||
"prebuilt_slack".to_string()
|
||||
)),
|
||||
StatusCode::FORBIDDEN
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reserved_name_is_bad_request() {
|
||||
assert_eq!(
|
||||
status(TemplateError::ReservedName("prebuilt_slack".to_string())),
|
||||
StatusCode::BAD_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user