// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

package google.cloud.chronicle.v1;

import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/resource.proto";
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
import "google/type/date.proto";
import "google/type/interval.proto";

option csharp_namespace = "Google.Cloud.Chronicle.V1";
option go_package = "cloud.google.com/go/chronicle/apiv1/chroniclepb;chroniclepb";
option java_multiple_files = true;
option java_outer_classname = "DashboardQueryProto";
option java_package = "com.google.cloud.chronicle.v1";
option php_namespace = "Google\\Cloud\\Chronicle\\V1";
option ruby_package = "Google::Cloud::Chronicle::V1";

// A service providing functionality for managing dashboards' queries.
service DashboardQueryService {
  option (google.api.default_host) = "chronicle.googleapis.com";
  option (google.api.oauth_scopes) =
      "https://www.googleapis.com/auth/chronicle,"
      "https://www.googleapis.com/auth/chronicle.readonly,"
      "https://www.googleapis.com/auth/cloud-platform";

  // Get a dashboard query.
  rpc GetDashboardQuery(GetDashboardQueryRequest) returns (DashboardQuery) {
    option (google.api.http) = {
      get: "/v1/{name=projects/*/locations/*/instances/*/dashboardQueries/*}"
    };
    option (google.api.method_signature) = "name";
  }

  // Execute a query and return the data.
  rpc ExecuteDashboardQuery(ExecuteDashboardQueryRequest)
      returns (ExecuteDashboardQueryResponse) {
    option (google.api.http) = {
      post: "/v1/{parent=projects/*/locations/*/instances/*}/dashboardQueries:execute"
      body: "*"
    };
    option (google.api.method_signature) = "parent,query";
  }
}

// A language feature describes a specific capability or syntax of the query
// language used in a dashboard query, such as `JOINS`, `STAGES`, or
// `DATA_TABLES`.
enum LanguageFeature {
  // Language feature is unknown.
  LANGUAGE_FEATURE_UNSPECIFIED = 0;

  // Language feature is joins.
  JOINS = 1;

  // Language feature is stages.
  STAGES = 2;

  // Language feature is data table.
  DATA_TABLES = 3;
}

enum FilterOperator {
  // Default unspecified.
  FILTER_OPERATOR_UNSPECIFIED = 0;

  EQUAL = 1;

  NOT_EQUAL = 2;

  IN = 3;

  GREATER_THAN = 4;

  GREATER_THAN_OR_EQUAL_TO = 5;

  LESS_THAN = 6;

  LESS_THAN_OR_EQUAL_TO = 7;

  BETWEEN = 8;

  PAST = 9;

  IS_NULL = 10;

  IS_NOT_NULL = 11;

  STARTS_WITH = 12;

  ENDS_WITH = 13;

  DOES_NOT_STARTS_WITH = 14;

  DOES_NOT_ENDS_WITH = 15;

  NOT_IN = 16;

  // CONTAINS is used for substring match.
  CONTAINS = 17;

  // Used if we want to check if the field does not contain the substring.
  DOES_NOT_CONTAIN = 18;
}

// LINT.IfChange(data_sources)
enum DataSource {
  DATA_SOURCE_UNSPECIFIED = 0;

  UDM = 1;

  ENTITY = 2;

  INGESTION_METRICS = 3;

  // RULE_DETECTIONS is used for detections datasource.
  RULE_DETECTIONS = 4;

  // RULESETS is used for ruleset with detections datasource.
  RULESETS = 5;

  // GLOBAL is used for standard time range filter.
  GLOBAL = 6;

  // IOC_MATCHES is used for ioc_matches datasource.
  IOC_MATCHES = 7;

  // RULES is used for rules datasource.
  RULES = 8;

  // SOAR Cases - identified as `case`.
  SOAR_CASES = 9;

  // SOAR Playbooks - identified as `playbook`.
  SOAR_PLAYBOOKS = 10;

  // SOAR Case History - identified as `case_history`.
  SOAR_CASE_HISTORY = 11;

  // DATA_TABLE is used for data tables source.
  DATA_TABLE = 12;

  // INVESTIGATION is used as the data source for triage agent investigations.
  // Identified as `gemini_investigation`.
  INVESTIGATION = 13;

  // INVESTIGATION_FEEDBACK is used as the data source for user feedback on
  // triage agent investigations. Identified as `gemini_investigation_feedback`.
  INVESTIGATION_FEEDBACK = 14;
}

// TimeUnit supported for PAST filter operator.
enum TimeUnit {
  // Default unspecified.
  TIME_UNIT_UNSPECIFIED = 0;

  SECOND = 1;

  MINUTE = 2;

  HOUR = 3;

  DAY = 4;

  WEEK = 5;

  MONTH = 6;

  YEAR = 7;
}

// DashboardQuery resource.
message DashboardQuery {
  option (google.api.resource) = {
    type: "chronicle.googleapis.com/DashboardQuery"
    pattern: "projects/{project}/locations/{location}/instances/{instance}/dashboardQueries/{query}"
    plural: "dashboardQueries"
    singular: "dashboardQuery"
  };

  // Input to the query like time window.
  message Input {
    // time representation for last x units.
    message RelativeTime {
      TimeUnit time_unit = 1 [(google.api.field_behavior) = REQUIRED];

      int64 start_time_val = 2 [(google.api.field_behavior) = REQUIRED];
    }

    oneof time_input {
      // time range to fetch the data for.
      google.type.Interval time_window = 1;

      // time range for last x units.
      RelativeTime relative_time = 3;
    }
  }

  // Output only. Name of the dashboardQuery.
  string name = 1 [
    (google.api.field_behavior) = OUTPUT_ONLY,
    (google.api.resource_reference) = {
      type: "chronicle.googleapis.com/DashboardQuery"
    }
  ];

  // Required. Search query string.
  string query = 2 [(google.api.field_behavior) = REQUIRED];

  // Required. Inputs to the query.
  Input input = 3 [(google.api.field_behavior) = REQUIRED];

  // Output only. DashboardChart this query belongs to.
  string dashboard_chart = 4 [
    (google.api.field_behavior) = OUTPUT_ONLY,
    (google.api.resource_reference) = {
      type: "chronicle.googleapis.com/DashboardChart"
    }
  ];

  // Optional. This checksum is computed by the server based on the value of
  // other fields, and may be sent on update and delete requests to ensure the
  // client has an up-to-date value before proceeding.
  string etag = 5 [(google.api.field_behavior) = OPTIONAL];
}

// Request message to get a dashboard query.
message GetDashboardQueryRequest {
  // Required. The name of the dashboardQuery to retrieve.
  // Format:
  // projects/{project}/locations/{location}/instances/{instance}/dashboardQueries/{query}
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {
      type: "chronicle.googleapis.com/DashboardQuery"
    }
  ];
}

// Request message to execute a dashboard query.
message ExecuteDashboardQueryRequest {
  // Required. The parent, under which to run this dashboardQuery.
  // Format: projects/{project}/locations/{location}/instances/{instance}
  string parent = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {
      child_type: "chronicle.googleapis.com/DashboardQuery"
    }
  ];

  // Required. The query to execute and get results back for.
  // QueryID or 'query', 'input.time_window' fields will be used. Use
  // 'native_dashboard' and 'dashboard_chart' fields if it is an in-dashboard
  // query.
  DashboardQuery query = 2 [(google.api.field_behavior) = REQUIRED];

  // Optional. Dashboard level filters other than query string.
  repeated DashboardFilter filters = 3 [(google.api.field_behavior) = OPTIONAL];

  // Optional. When true, the backend would read from the database, rather than
  // fetching data directly from the cache.
  bool clear_cache = 4 [(google.api.field_behavior) = OPTIONAL];

  // Optional. When true, the backend will execute the query against the
  // previous time range of the query.
  bool use_previous_time_range = 5 [(google.api.field_behavior) = OPTIONAL];
}

// Runtime error for a dashboard query.
message QueryRuntimeError {
  // Metadata for the error.
  message QueryRuntimeErrorMetadata {
    // Metadata key.
    MetadataKey key = 1;

    // Metadata value.
    string value = 2;
  }

  // Based on ErrorSeverity, UI will choose to format the error differently.
  enum ErrorSeverity {
    // Severity is unknown.
    ERROR_SEVERITY_UNSPECIFIED = 0;

    // Severity is warning.
    WARNING = 1;

    // Error is severe.
    SEVERE = 2;
  }

  // Metadata enum to identify the metadata key.
  enum MetadataKey {
    // Key is unknown.
    METADATA_KEY_UNSPECIFIED = 0;

    // Key is row limit.
    ROW_LIMIT = 1;
  }

  // Warning reason.
  enum WarningReason {
    // Reason is unknown.
    WARNING_REASON_UNSPECIFIED = 0;

    // Reason is row limit exceeded.
    ROW_LIMIT_EXCEEDED = 1;

    // Reason is default row limit exceeded.
    DEFAULT_ROW_LIMIT_EXCEEDED = 2;

    // Reason is curated query default row limit exceeded.
    CURATED_QUERY_DEFAULT_ROW_LIMIT_EXCEEDED = 3;
  }

  // Short Description of the error.
  string error_title = 1;

  // Error message
  string error_description = 2;

  // Severity of the error.
  ErrorSeverity error_severity = 3;

  // Metadata for the error.
  repeated QueryRuntimeErrorMetadata metadata = 4;

  // Reason for the error.
  WarningReason warning_reason = 5;
}

// Response message for executing a dashboard query.
message ExecuteDashboardQueryResponse {
  // LINT.IfChange(stats_data)
  // Value of the column based on data type.
  message ColumnValue {
    message ValueMetadata {
      // "Auto" generated In-app links.
      repeated InAppLink links = 1;

      repeated string field_paths = 2;

      // Timestamp value to store the timestamp for the case of the date and
      // time data type.
      google.protobuf.Timestamp timestamp_val = 3;
    }

    oneof value {
      // True if the value is NULL.
      bool null_val = 1;

      // Boolean value.
      bool bool_val = 2;

      // Bytes value.
      bytes bytes_val = 3;

      // Double value.
      double double_val = 4;

      // Integer value (signed).
      int64 int64_val = 5;

      // Un-signed integer value.
      uint64 uint64_val = 6;

      // String value. Enum values are returned as strings.
      string string_val = 7;

      // Timestamp values. Does not handle `interval`.
      google.protobuf.Timestamp timestamp_val = 8;

      // Date values.
      google.type.Date date_val = 9;

      // For any proto values that are not any of the above.
      google.protobuf.Any proto_val = 10;
    }

    ValueMetadata metadata = 11;
  }

  // Singular vs list of values in a column.
  message ColumnType {
    // Store list of values in a column.
    message List {
      repeated ColumnValue values = 1;
    }

    oneof type {
      // Single value in a column.
      ColumnValue value = 1;

      // List of values in a column e.g. IPs
      List list = 2;
    }
  }

  message ColumnData {
    // Used to store column names.
    string column = 1;

    // To store column data.
    repeated ColumnType values = 2;

    // To store column metadata.
    ColumnMetadata metadata = 3;
  }

  // Result rows that are queried.
  repeated ColumnData results = 1;

  // Datasource of the query and results.
  repeated DataSource data_sources = 3;

  // Optional. Last time the cache was refreshed. This would be used by the UI
  // to show the last updated time.
  google.protobuf.Timestamp last_backend_cache_refreshed_time = 4
      [(google.api.field_behavior) = OPTIONAL];

  // Time window against which query was executed.
  google.type.Interval time_window = 5;

  // Runtime errors
  repeated QueryRuntimeError query_runtime_errors = 6;

  // Optional. Language features found in the query.
  repeated LanguageFeature language_features = 7
      [(google.api.field_behavior) = OPTIONAL];
}

// Dashboard level filter that can be used in native dashboards as well as
// inputs to execute query.
message DashboardFilter {
  // ID of the filter.
  string id = 1;

  // Datasource the filter is applicable for.
  DataSource data_source = 2;

  // Filter field path.
  string field_path = 3;

  // Operator and values. Can include multiple modifiers.
  repeated FilterOperatorAndValues filter_operator_and_field_values = 4;

  // Display name of the filter.
  string display_name = 5;

  // Chart IDs the filter is applicable for.
  repeated string chart_ids = 6;

  // Optional. Whether the filter is a standard time range filter,
  // meaning that it has to be used as the query time range,
  // and not as a predicate in the query.
  // A chart can have at most one standard time range filter applied.
  bool is_standard_time_range_filter = 7
      [(google.api.field_behavior) = OPTIONAL];

  // Optional. Whether this filter is required to be populated by the
  // dashboard consumer prior to the dashboard loading.
  bool is_mandatory = 8 [(google.api.field_behavior) = OPTIONAL];

  // Optional. Whether this standard time range filter is enabled.
  optional bool is_standard_time_range_filter_enabled = 9
      [(google.api.field_behavior) = OPTIONAL];

  // Optional. Advanced filter configuration for the filter widget.
  AdvancedFilterConfig advanced_filter_config = 10
      [(google.api.field_behavior) = OPTIONAL];
}

message FilterOperatorAndValues {
  // Operator for a single filter modifier.
  FilterOperator filter_operator = 1;

  // Values for the modifier. All operators should have a single value other
  // than 'IN' and 'BETWEEN'. 'PAST' will have negative seconds
  // like -86400 is past 1 day.
  repeated string field_values = 2;
}

// Advanced filter configuration for the filter widget.
message AdvancedFilterConfig {
  // Source of the values for the filter.
  message ValueSource {
    // Source of the values for the filter.
    oneof source {
      // Optional. Manual options provided by the user.
      ManualOptions manual_options = 1 [(google.api.field_behavior) = OPTIONAL];

      // Optional. Query options to fetch the values from the query engine.
      // This is used for the filter's population query.
      QueryOptions query_options = 2 [(google.api.field_behavior) = OPTIONAL];
    }
  }

  // Manual options provided by the user.
  message ManualOptions {
    // Optional. The options provided by the user.
    // The max number of options is limited to 10000.
    repeated string options = 1 [(google.api.field_behavior) = OPTIONAL];
  }

  // Query options to fetch the values from the query engine.
  // This is used for the filter's population query.
  message QueryOptions {
    // Required. The query to execute to fetch the values.
    string query = 1 [(google.api.field_behavior) = REQUIRED];

    // Required. The column name to use for the values.
    string column = 2 [(google.api.field_behavior) = REQUIRED];

    // Optional. Enable global time filter
    bool global_time_filter_enabled = 3
        [(google.api.field_behavior) = OPTIONAL];

    // Optional. Time range input specifically for the filter's population
    // query.
    DashboardQuery.Input input = 4 [(google.api.field_behavior) = OPTIONAL];
  }

  // Required. The token name to look for in the query (e.g., "hostname").
  // The system will automatically wrap this in '$' (e.g., "$hostname$").
  string token = 1 [(google.api.field_behavior) = REQUIRED];

  // Optional. String to prepend to the final replaced value (e.g., "/", "^(",
  // "\"").
  string prefix = 2 [(google.api.field_behavior) = OPTIONAL];

  // Optional. String to append to the final replaced value (e.g., "/", ")$",
  // "\"").
  string suffix = 3 [(google.api.field_behavior) = OPTIONAL];

  // Optional. Delimiter to join multiple selected values (e.g., "|", " OR field
  // = ").
  string separator = 4 [(google.api.field_behavior) = OPTIONAL];

  // Optional. Whether to allow selection of multiple values.
  bool multiple_allowed = 5 [(google.api.field_behavior) = OPTIONAL];

  // Optional. Default values to use if no value is selected/provided.
  repeated string default_values = 6 [(google.api.field_behavior) = OPTIONAL];

  // Optional. Whether to skip the configured prefix and suffix when using
  // default values. If true, default values are inserted raw (joined by the
  // separator).
  bool skip_default_affixes = 7 [(google.api.field_behavior) = OPTIONAL];

  // Required. Source of the values for the filter.
  ValueSource value_source = 8 [(google.api.field_behavior) = REQUIRED];
}

// In app linking start
message InAppLink {
  // URL to redirect to.
  string url = 1;

  // Label for the link.
  string label = 2;

  // Icon url for the link.
  string icon_url = 3;
}

// Metadata of the column.
message ColumnMetadata {
  // Name of the column.
  string column = 1;

  // Field path of the queried field, if any.
  string field_path = 2;

  // Name of the function used to query the field, if any.
  string function_name = 3;

  // Module of the function used to query the field, if any.
  string function_module = 4;

  // Data source queried.
  DataSource data_source = 5;

  // Timestamp Metadata
  TimestampMetadata timestamp_metadata = 6;

  // Whether the column is a longitude field.
  bool longitude = 7;

  // Whether the column is a latitude field.
  bool latitude = 8;

  // Whether the column is selected in the final response.
  bool selected = 9;

  // Whether the column is unselected in the final response.
  bool unselected = 10;
}

// Metadata of the timestamp column.
message TimestampMetadata {
  // Time format of the timestamp column.
  string time_format = 1;

  // Time zone of the timestamp column.
  string time_zone = 2;

  // Time granularity of the timestamp column.
  string time_granularity = 3;

  // Whether the timestamp column is sortable in UI.
  bool is_sortable = 4;

  // Whether the timestamp column is interpolable in UI.
  bool is_interpolable = 5;
}
