// Copyright 2025 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.discoveryengine.v1;

import "google/api/field_behavior.proto";
import "google/api/resource.proto";
import "google/cloud/discoveryengine/v1/common.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";

option csharp_namespace = "Google.Cloud.DiscoveryEngine.V1";
option go_package = "cloud.google.com/go/discoveryengine/apiv1/discoveryenginepb;discoveryenginepb";
option java_multiple_files = true;
option java_outer_classname = "UserEventProto";
option java_package = "com.google.cloud.discoveryengine.v1";
option objc_class_prefix = "DISCOVERYENGINE";
option php_namespace = "Google\\Cloud\\DiscoveryEngine\\V1";
option ruby_package = "Google::Cloud::DiscoveryEngine::V1";

// UserEvent captures all metadata information Discovery Engine API needs to
// know about how end users interact with your website.
message UserEvent {
  // Required. User event type. Allowed values are:
  //
  // Generic values:
  //
  // * `search`: Search for Documents.
  // * `view-item`: Detailed page view of a Document.
  // * `view-item-list`: View of a panel or ordered list of Documents.
  // * `view-home-page`: View of the home page.
  // * `view-category-page`: View of a category page, e.g. Home > Men > Jeans
  // * `add-feedback`: Add a user feedback.
  //
  // Retail-related values:
  //
  // * `add-to-cart`: Add an item(s) to cart, e.g. in Retail online shopping
  // * `purchase`: Purchase an item(s)
  //
  // Media-related values:
  //
  // * `media-play`: Start/resume watching a video, playing a song, etc.
  // * `media-complete`: Finished or stopped midway through a video, song, etc.
  //
  // Custom conversion value:
  //
  // * `conversion`: Customer defined conversion event.
  string event_type = 1 [(google.api.field_behavior) = REQUIRED];

  // Optional. Conversion type.
  //
  // Required if
  // [UserEvent.event_type][google.cloud.discoveryengine.v1.UserEvent.event_type]
  // is `conversion`. This is a customer-defined conversion name in lowercase
  // letters or numbers separated by "-", such as "watch", "good-visit" etc.
  //
  // Do not set the field if
  // [UserEvent.event_type][google.cloud.discoveryengine.v1.UserEvent.event_type]
  // is not `conversion`. This mixes the custom conversion event with predefined
  // events like `search`, `view-item` etc.
  string conversion_type = 21 [(google.api.field_behavior) = OPTIONAL];

  // Required. A unique identifier for tracking visitors.
  //
  // For example, this could be implemented with an HTTP cookie, which should be
  // able to uniquely identify a visitor on a single device. This unique
  // identifier should not change if the visitor log in/out of the website.
  //
  // Do not set the field to the same fixed ID for different users. This mixes
  // the event history of those users together, which results in degraded model
  // quality.
  //
  // The field must be a UTF-8 encoded string with a length limit of 128
  // characters. Otherwise, an `INVALID_ARGUMENT` error is returned.
  //
  // The field should not contain PII or user-data. We recommend to use Google
  // Analytics [Client
  // ID](https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#clientId)
  // for this field.
  string user_pseudo_id = 2 [(google.api.field_behavior) = REQUIRED];

  // The [Engine][google.cloud.discoveryengine.v1.Engine] resource name, in the
  // form of
  // `projects/{project}/locations/{location}/collections/{collection_id}/engines/{engine_id}`.
  //
  // Optional. Only required for
  // [Engine][google.cloud.discoveryengine.v1.Engine] produced user events. For
  // example, user events from blended search.
  string engine = 19 [(google.api.resource_reference) = {
    type: "discoveryengine.googleapis.com/Engine"
  }];

  // The [DataStore][google.cloud.discoveryengine.v1.DataStore] resource full
  // name, of the form
  // `projects/{project}/locations/{location}/collections/{collection_id}/dataStores/{data_store_id}`.
  //
  // Optional. Only required for user events whose data store can't by
  // determined by
  // [UserEvent.engine][google.cloud.discoveryengine.v1.UserEvent.engine] or
  // [UserEvent.documents][google.cloud.discoveryengine.v1.UserEvent.documents].
  // If data store is set in the parent of write/import/collect user event
  // requests, this field can be omitted.
  string data_store = 20 [(google.api.resource_reference) = {
    type: "discoveryengine.googleapis.com/DataStore"
  }];

  // Only required for
  // [UserEventService.ImportUserEvents][google.cloud.discoveryengine.v1.UserEventService.ImportUserEvents]
  // method. Timestamp of when the user event happened.
  google.protobuf.Timestamp event_time = 3;

  // Information about the end user.
  UserInfo user_info = 4;

  // Should set to true if the request is made directly from the end user, in
  // which case the
  // [UserEvent.user_info.user_agent][google.cloud.discoveryengine.v1.UserInfo.user_agent]
  // can be populated from the HTTP request.
  //
  // This flag should be set only if the API request is made directly from the
  // end user such as a mobile app (and not if a gateway or a server is
  // processing and pushing the user events).
  //
  // This should not be set when using the JavaScript tag in
  // [UserEventService.CollectUserEvent][google.cloud.discoveryengine.v1.UserEventService.CollectUserEvent].
  bool direct_user_request = 5;

  // A unique identifier for tracking a visitor session with a length limit of
  // 128 bytes. A session is an aggregation of an end user behavior in a time
  // span.
  //
  // A general guideline to populate the session_id:
  //
  // 1. If user has no activity for 30 min, a new session_id should be assigned.
  // 2. The session_id should be unique across users, suggest use uuid or add
  // [UserEvent.user_pseudo_id][google.cloud.discoveryengine.v1.UserEvent.user_pseudo_id]
  // as prefix.
  string session_id = 6;

  // Page metadata such as categories and other critical information for certain
  // event types such as `view-category-page`.
  PageInfo page_info = 7;

  // Token to attribute an API response to user action(s) to trigger the event.
  //
  // Highly recommended for user events that are the result of
  // [RecommendationService.Recommend][google.cloud.discoveryengine.v1.RecommendationService.Recommend].
  // This field enables accurate attribution of recommendation model
  // performance.
  //
  // The value must be one of:
  //
  // * [RecommendResponse.attribution_token][google.cloud.discoveryengine.v1.RecommendResponse.attribution_token] for events that are the result of
  // [RecommendationService.Recommend][google.cloud.discoveryengine.v1.RecommendationService.Recommend].
  // * [SearchResponse.attribution_token][google.cloud.discoveryengine.v1.SearchResponse.attribution_token] for events that are the result of
  // [SearchService.Search][google.cloud.discoveryengine.v1.SearchService.Search].
  //
  // This token enables us to accurately attribute page view or conversion
  // completion back to the event and the particular predict response containing
  // this clicked/purchased product. If user clicks on product K in the
  // recommendation results, pass
  // [RecommendResponse.attribution_token][google.cloud.discoveryengine.v1.RecommendResponse.attribution_token]
  // as a URL parameter to product K's page. When recording events on product
  // K's page, log the
  // [RecommendResponse.attribution_token][google.cloud.discoveryengine.v1.RecommendResponse.attribution_token]
  // to this field.
  string attribution_token = 8;

  // The filter syntax consists of an expression language for constructing a
  // predicate from one or more fields of the documents being filtered.
  //
  // One example is for `search` events, the associated
  // [SearchRequest][google.cloud.discoveryengine.v1.SearchRequest] may contain
  // a filter expression in
  // [SearchRequest.filter][google.cloud.discoveryengine.v1.SearchRequest.filter]
  // conforming to https://google.aip.dev/160#filtering.
  //
  // Similarly, for `view-item-list` events that are generated from a
  // [RecommendRequest][google.cloud.discoveryengine.v1.RecommendRequest], this
  // field may be populated directly from
  // [RecommendRequest.filter][google.cloud.discoveryengine.v1.RecommendRequest.filter]
  // conforming to https://google.aip.dev/160#filtering.
  //
  // The value must be a UTF-8 encoded string with a length limit of 1,000
  // characters. Otherwise, an `INVALID_ARGUMENT` error is returned.
  string filter = 9;

  // List of [Document][google.cloud.discoveryengine.v1.Document]s associated
  // with this user event.
  //
  // This field is optional except for the following event types:
  //
  // * `view-item`
  // * `add-to-cart`
  // * `purchase`
  // * `media-play`
  // * `media-complete`
  //
  // In a `search` event, this field represents the documents returned to the
  // end user on the current page (the end user may have not finished browsing
  // the whole page yet). When a new page is returned to the end user, after
  // pagination/filtering/ordering even for the same query, a new `search` event
  // with different
  // [UserEvent.documents][google.cloud.discoveryengine.v1.UserEvent.documents]
  // is desired.
  repeated DocumentInfo documents = 10;

  // Panel metadata associated with this user event.
  PanelInfo panel = 11;

  // [SearchService.Search][google.cloud.discoveryengine.v1.SearchService.Search]
  // details related to the event.
  //
  // This field should be set for `search` event.
  SearchInfo search_info = 12;

  // [CompletionService.CompleteQuery][google.cloud.discoveryengine.v1.CompletionService.CompleteQuery]
  // details related to the event.
  //
  // This field should be set for `search` event when autocomplete function is
  // enabled and the user clicks a suggestion for search.
  CompletionInfo completion_info = 13;

  // The transaction metadata (if any) associated with this user event.
  TransactionInfo transaction_info = 14;

  // A list of identifiers for the independent experiment groups this user event
  // belongs to. This is used to distinguish between user events associated with
  // different experiment setups.
  repeated string tag_ids = 15;

  // The promotion IDs if this is an event associated with promotions.
  // Currently, this field is restricted to at most one ID.
  repeated string promotion_ids = 16;

  // Extra user event features to include in the recommendation model.
  // These attributes must NOT contain data that needs to be parsed or processed
  // further, e.g. JSON or other encodings.
  //
  // If you provide custom attributes for ingested user events, also include
  // them in the user events that you associate with prediction requests. Custom
  // attribute formatting must be consistent between imported events and events
  // provided with prediction requests. This lets the Discovery Engine API use
  // those custom attributes when training models and serving predictions, which
  // helps improve recommendation quality.
  //
  // This field needs to pass all below criteria, otherwise an
  // `INVALID_ARGUMENT` error is returned:
  //
  // * The key must be a UTF-8 encoded string with a length limit of 5,000
  //   characters.
  // * For text attributes, at most 400 values are allowed. Empty values are not
  //   allowed. Each value must be a UTF-8 encoded string with a length limit of
  //   256 characters.
  // * For number attributes, at most 400 values are allowed.
  //
  // For product recommendations, an example of extra user information is
  // `traffic_channel`, which is how a user arrives at the site. Users can
  // arrive
  // at the site by coming to the site directly, coming through Google
  // search, or in other ways.
  map<string, CustomAttribute> attributes = 17;

  // Media-specific info.
  MediaInfo media_info = 18;

  // Optional. List of panels associated with this event.
  // Used for page-level impression data.
  repeated PanelInfo panels = 22 [(google.api.field_behavior) = OPTIONAL];
}

// Detailed page information.
message PageInfo {
  // A unique ID of a web page view.
  //
  // This should be kept the same for all user events triggered from the same
  // pageview. For example, an item detail page view could trigger multiple
  // events as the user is browsing the page. The `pageview_id` property should
  // be kept the same for all these events so that they can be grouped together
  // properly.
  //
  // When using the client side event reporting with JavaScript pixel and Google
  // Tag Manager, this value is filled in automatically.
  string pageview_id = 1;

  // The most specific category associated with a category page.
  //
  // To represent full path of category, use '>' sign to separate different
  // hierarchies. If '>' is part of the category name, replace it with
  // other character(s).
  //
  // Category pages include special pages such as sales or promotions. For
  // instance, a special sale page may have the category hierarchy:
  // `"pageCategory" : "Sales > 2017 Black Friday Deals"`.
  //
  // Required for `view-category-page` events. Other event types should not set
  // this field. Otherwise, an `INVALID_ARGUMENT` error is returned.
  string page_category = 2;

  // Complete URL (window.location.href) of the user's current page.
  //
  // When using the client side event reporting with JavaScript pixel and Google
  // Tag Manager, this value is filled in automatically. Maximum length 5,000
  // characters.
  string uri = 3;

  // The referrer URL of the current page.
  //
  // When using the client side event reporting with JavaScript pixel and Google
  // Tag Manager, this value is filled in automatically. However, some browser
  // privacy restrictions may cause this field to be empty.
  string referrer_uri = 4;
}

// Detailed search information.
message SearchInfo {
  // The user's search query.
  //
  // See
  // [SearchRequest.query][google.cloud.discoveryengine.v1.SearchRequest.query]
  // for definition.
  //
  // The value must be a UTF-8 encoded string with a length limit of 5,000
  // characters. Otherwise, an `INVALID_ARGUMENT` error is returned.
  //
  // At least one of
  // [search_query][google.cloud.discoveryengine.v1.SearchInfo.search_query] or
  // [PageInfo.page_category][google.cloud.discoveryengine.v1.PageInfo.page_category]
  // is required for `search` events. Other event types should not set this
  // field. Otherwise, an `INVALID_ARGUMENT` error is returned.
  string search_query = 1;

  // The order in which products are returned, if applicable.
  //
  // See
  // [SearchRequest.order_by][google.cloud.discoveryengine.v1.SearchRequest.order_by]
  // for definition and syntax.
  //
  // The value must be a UTF-8 encoded string with a length limit of 1,000
  // characters. Otherwise, an `INVALID_ARGUMENT` error is returned.
  //
  // This can only be set for `search` events. Other event types should not set
  // this field. Otherwise, an `INVALID_ARGUMENT` error is returned.
  string order_by = 2;

  // An integer that specifies the current offset for pagination (the 0-indexed
  // starting location, amongst the products deemed by the API as relevant).
  //
  // See
  // [SearchRequest.offset][google.cloud.discoveryengine.v1.SearchRequest.offset]
  // for definition.
  //
  // If this field is negative, an `INVALID_ARGUMENT` is returned.
  //
  // This can only be set for `search` events. Other event types should not set
  // this field. Otherwise, an `INVALID_ARGUMENT` error is returned.
  optional int32 offset = 3;
}

// Detailed completion information including completion attribution token and
// clicked completion info.
message CompletionInfo {
  // End user selected
  // [CompleteQueryResponse.QuerySuggestion.suggestion][google.cloud.discoveryengine.v1.CompleteQueryResponse.QuerySuggestion.suggestion].
  string selected_suggestion = 1;

  // End user selected
  // [CompleteQueryResponse.QuerySuggestion.suggestion][google.cloud.discoveryengine.v1.CompleteQueryResponse.QuerySuggestion.suggestion]
  // position, starting from 0.
  int32 selected_position = 2;
}

// A transaction represents the entire purchase transaction.
message TransactionInfo {
  // Required. Total non-zero value associated with the transaction. This value
  // may include shipping, tax, or other adjustments to the total value that you
  // want to include.
  optional float value = 1 [(google.api.field_behavior) = REQUIRED];

  // Required. Currency code. Use three-character ISO-4217 code.
  string currency = 2 [(google.api.field_behavior) = REQUIRED];

  // The transaction ID with a length limit of 128 characters.
  string transaction_id = 3;

  // All the taxes associated with the transaction.
  optional float tax = 4;

  // All the costs associated with the products. These can be manufacturing
  // costs, shipping expenses not borne by the end user, or any other costs,
  // such that:
  //
  // * Profit = [value][google.cloud.discoveryengine.v1.TransactionInfo.value] -
  // [tax][google.cloud.discoveryengine.v1.TransactionInfo.tax] -
  // [cost][google.cloud.discoveryengine.v1.TransactionInfo.cost]
  optional float cost = 5;

  // The total discount(s) value applied to this transaction.
  // This figure should be excluded from
  // [TransactionInfo.value][google.cloud.discoveryengine.v1.TransactionInfo.value]
  //
  // For example, if a user paid
  // [TransactionInfo.value][google.cloud.discoveryengine.v1.TransactionInfo.value]
  // amount, then nominal (pre-discount) value of the transaction is the sum of
  // [TransactionInfo.value][google.cloud.discoveryengine.v1.TransactionInfo.value]
  // and
  // [TransactionInfo.discount_value][google.cloud.discoveryengine.v1.TransactionInfo.discount_value]
  //
  // This means that profit is calculated the same way, regardless of the
  // discount value, and that
  // [TransactionInfo.discount_value][google.cloud.discoveryengine.v1.TransactionInfo.discount_value]
  // can be larger than
  // [TransactionInfo.value][google.cloud.discoveryengine.v1.TransactionInfo.value]:
  //
  // * Profit = [value][google.cloud.discoveryengine.v1.TransactionInfo.value] -
  // [tax][google.cloud.discoveryengine.v1.TransactionInfo.tax] -
  // [cost][google.cloud.discoveryengine.v1.TransactionInfo.cost]
  optional float discount_value = 6;
}

// Detailed document information associated with a user event.
message DocumentInfo {
  // A required descriptor of the associated
  // [Document][google.cloud.discoveryengine.v1.Document].
  //
  // * If [id][google.cloud.discoveryengine.v1.DocumentInfo.id] is specified,
  // then the default values for
  // `{location}`, `{collection_id}`, `{data_store_id}`, and `{branch_id}` are
  // used when annotating with the stored Document.
  //
  // * If [name][google.cloud.discoveryengine.v1.DocumentInfo.name] is
  // specified, then the provided values (default values allowed) for
  // `{location}`, `{collection_id}`, `{data_store_id}`, and
  // `{branch_id}` are used when annotating with the stored Document.
  oneof document_descriptor {
    // The [Document][google.cloud.discoveryengine.v1.Document] resource ID.
    string id = 1;

    // The [Document][google.cloud.discoveryengine.v1.Document] resource full
    // name, of the form:
    // `projects/{project}/locations/{location}/collections/{collection_id}/dataStores/{data_store_id}/branches/{branch_id}/documents/{document_id}`
    string name = 2 [(google.api.resource_reference) = {
      type: "discoveryengine.googleapis.com/Document"
    }];

    // The [Document][google.cloud.discoveryengine.v1.Document] URI - only
    // allowed for website data stores.
    string uri = 6;
  }

  // Quantity of the Document associated with the user event. Defaults to 1.
  //
  // For example, this field is 2 if two quantities of the same Document
  // are involved in a `add-to-cart` event.
  //
  // Required for events of the following event types:
  //
  // * `add-to-cart`
  // * `purchase`
  optional int32 quantity = 3;

  // The promotion IDs associated with this Document.
  // Currently, this field is restricted to at most one ID.
  repeated string promotion_ids = 4;

  // Output only. Whether the referenced Document can be found in the data
  // store.
  bool joined = 5 [(google.api.field_behavior) = OUTPUT_ONLY];

  // Optional. The conversion value associated with this Document.
  // Must be set if
  // [UserEvent.event_type][google.cloud.discoveryengine.v1.UserEvent.event_type]
  // is "conversion".
  //
  // For example, a value of 1000 signifies that 1000 seconds were spent viewing
  // a Document for the `watch` conversion type.
  optional float conversion_value = 7 [(google.api.field_behavior) = OPTIONAL];
}

// Detailed panel information associated with a user event.
message PanelInfo {
  // Required. The panel ID.
  string panel_id = 2 [(google.api.field_behavior) = REQUIRED];

  // The display name of the panel.
  string display_name = 3;

  // The ordered position of the panel, if shown to the user with other panels.
  // If set, then
  // [total_panels][google.cloud.discoveryengine.v1.PanelInfo.total_panels] must
  // also be set.
  optional int32 panel_position = 4;

  // The total number of panels, including this one, shown to the user.
  // Must be set if
  // [panel_position][google.cloud.discoveryengine.v1.PanelInfo.panel_position]
  // is set.
  optional int32 total_panels = 5;

  // Optional. The document IDs associated with this panel.
  repeated DocumentInfo documents = 6 [(google.api.field_behavior) = OPTIONAL];
}

// Media-specific user event information.
message MediaInfo {
  // The media progress time in seconds, if applicable.
  // For example, if the end user has finished 90 seconds of a playback video,
  // then
  // [MediaInfo.media_progress_duration.seconds][google.protobuf.Duration.seconds]
  // should be set to 90.
  google.protobuf.Duration media_progress_duration = 1;

  // Media progress should be computed using only the
  // [media_progress_duration][google.cloud.discoveryengine.v1.MediaInfo.media_progress_duration]
  // relative to the media total length.
  //
  // This value must be between `[0, 1.0]` inclusive.
  //
  // If this is not a playback or the progress cannot be computed (e.g. ongoing
  // livestream), this field should be unset.
  optional float media_progress_percentage = 2;
}
