diff --git a/google/cloud/contact_center_insights/__init__.py b/google/cloud/contact_center_insights/__init__.py index ce5ce5f..4f3f328 100644 --- a/google/cloud/contact_center_insights/__init__.py +++ b/google/cloud/contact_center_insights/__init__.py @@ -135,6 +135,9 @@ from google.cloud.contact_center_insights_v1.types.resources import ( ConversationLevelSentiment, ) +from google.cloud.contact_center_insights_v1.types.resources import ( + ConversationParticipant, +) from google.cloud.contact_center_insights_v1.types.resources import DialogflowIntent from google.cloud.contact_center_insights_v1.types.resources import ( DialogflowInteractionData, @@ -215,6 +218,7 @@ "Conversation", "ConversationDataSource", "ConversationLevelSentiment", + "ConversationParticipant", "DialogflowIntent", "DialogflowInteractionData", "DialogflowSource", diff --git a/google/cloud/contact_center_insights_v1/__init__.py b/google/cloud/contact_center_insights_v1/__init__.py index 9d60f3d..5610124 100644 --- a/google/cloud/contact_center_insights_v1/__init__.py +++ b/google/cloud/contact_center_insights_v1/__init__.py @@ -59,6 +59,7 @@ from .types.resources import Conversation from .types.resources import ConversationDataSource from .types.resources import ConversationLevelSentiment +from .types.resources import ConversationParticipant from .types.resources import DialogflowIntent from .types.resources import DialogflowInteractionData from .types.resources import DialogflowSource @@ -104,6 +105,7 @@ "Conversation", "ConversationDataSource", "ConversationLevelSentiment", + "ConversationParticipant", "ConversationView", "CreateAnalysisOperationMetadata", "CreateAnalysisRequest", diff --git a/google/cloud/contact_center_insights_v1/services/contact_center_insights/client.py b/google/cloud/contact_center_insights_v1/services/contact_center_insights/client.py index 68b5b0c..64ed034 100644 --- a/google/cloud/contact_center_insights_v1/services/contact_center_insights/client.py +++ b/google/cloud/contact_center_insights_v1/services/contact_center_insights/client.py @@ -440,6 +440,10 @@ def __init__( client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, + always_use_jwt_access=( + Transport == type(self).get_transport_class("grpc") + or Transport == type(self).get_transport_class("grpc_asyncio") + ), ) def create_conversation( diff --git a/google/cloud/contact_center_insights_v1/types/__init__.py b/google/cloud/contact_center_insights_v1/types/__init__.py index cae1ef2..a7af4bf 100644 --- a/google/cloud/contact_center_insights_v1/types/__init__.py +++ b/google/cloud/contact_center_insights_v1/types/__init__.py @@ -58,6 +58,7 @@ Conversation, ConversationDataSource, ConversationLevelSentiment, + ConversationParticipant, DialogflowIntent, DialogflowInteractionData, DialogflowSource, @@ -131,6 +132,7 @@ "Conversation", "ConversationDataSource", "ConversationLevelSentiment", + "ConversationParticipant", "DialogflowIntent", "DialogflowInteractionData", "DialogflowSource", diff --git a/google/cloud/contact_center_insights_v1/types/contact_center_insights.py b/google/cloud/contact_center_insights_v1/types/contact_center_insights.py index 0228d0c..5dd356a 100644 --- a/google/cloud/contact_center_insights_v1/types/contact_center_insights.py +++ b/google/cloud/contact_center_insights_v1/types/contact_center_insights.py @@ -111,8 +111,48 @@ class CalculateStatsResponse(proto.Message): respective number of matches in the set of conversations. Key has the format: ``projects//locations//issueModels//issues/`` + conversation_count_time_series (google.cloud.contact_center_insights_v1.types.CalculateStatsResponse.TimeSeries): + A time series representing the count of + conversations created over time that match that + requested filter criteria. """ + class TimeSeries(proto.Message): + r"""A time series representing conversations over time. + Attributes: + interval_duration (google.protobuf.duration_pb2.Duration): + The duration of each interval. + points (Sequence[google.cloud.contact_center_insights_v1.types.CalculateStatsResponse.TimeSeries.Interval]): + An ordered list of intervals from earliest to + latest, where each interval represents the + number of conversations that transpired during + the time window. + """ + + class Interval(proto.Message): + r"""A single interval in a time series. + Attributes: + start_time (google.protobuf.timestamp_pb2.Timestamp): + The start time of this interval. + conversation_count (int): + The number of conversations created in this + interval. + """ + + start_time = proto.Field( + proto.MESSAGE, number=1, message=timestamp_pb2.Timestamp, + ) + conversation_count = proto.Field(proto.INT32, number=2,) + + interval_duration = proto.Field( + proto.MESSAGE, number=1, message=duration_pb2.Duration, + ) + points = proto.RepeatedField( + proto.MESSAGE, + number=2, + message="CalculateStatsResponse.TimeSeries.Interval", + ) + average_duration = proto.Field( proto.MESSAGE, number=1, message=duration_pb2.Duration, ) @@ -121,6 +161,9 @@ class CalculateStatsResponse(proto.Message): smart_highlighter_matches = proto.MapField(proto.STRING, proto.INT32, number=4,) custom_highlighter_matches = proto.MapField(proto.STRING, proto.INT32, number=5,) issue_matches = proto.MapField(proto.STRING, proto.INT32, number=6,) + conversation_count_time_series = proto.Field( + proto.MESSAGE, number=7, message=TimeSeries, + ) class CreateAnalysisOperationMetadata(proto.Message): @@ -384,6 +427,11 @@ class ExportInsightsDataRequest(proto.Message): class BigQueryDestination(proto.Message): r"""A BigQuery Table Reference. Attributes: + project_id (str): + A project ID or number. If specified, then + export will attempt to write data to this + project instead of the resource project. + Otherwise, the resource project will be used. dataset (str): Required. The name of the BigQuery dataset that the snapshot result should be exported to. If this dataset does not @@ -394,6 +442,7 @@ class BigQueryDestination(proto.Message): returns an INVALID_ARGUMENT error. """ + project_id = proto.Field(proto.STRING, number=3,) dataset = proto.Field(proto.STRING, number=1,) table = proto.Field(proto.STRING, number=2,) diff --git a/google/cloud/contact_center_insights_v1/types/resources.py b/google/cloud/contact_center_insights_v1/types/resources.py index 0f87b19..6f7c094 100644 --- a/google/cloud/contact_center_insights_v1/types/resources.py +++ b/google/cloud/contact_center_insights_v1/types/resources.py @@ -59,6 +59,7 @@ "SmartReplyData", "SmartComposeSuggestionData", "DialogflowInteractionData", + "ConversationParticipant", }, ) @@ -176,7 +177,7 @@ class TranscriptSegment(proto.Message): audioChannelCount = N, its output values can range from '1' to 'N'. A channel tag of 0 indicates that the audio is mono. - participant (google.cloud.contact_center_insights_v1.types.Conversation.Transcript.TranscriptSegment.Participant): + segment_participant (google.cloud.contact_center_insights_v1.types.ConversationParticipant): The participant of this segment. """ @@ -208,31 +209,6 @@ class WordInfo(proto.Message): word = proto.Field(proto.STRING, number=3,) confidence = proto.Field(proto.FLOAT, number=4,) - class Participant(proto.Message): - r"""The participant of the transcript segment. - Attributes: - dialogflow_participant (str): - The name of the Dialogflow participant. - Format: - projects/{project}/locations/{location}/conversations/{conversation}/participants/{participant} - role (google.cloud.contact_center_insights_v1.types.Conversation.Transcript.TranscriptSegment.Participant.Role): - The role of the participant. - """ - - class Role(proto.Enum): - r"""The role of the participant.""" - ROLE_UNSPECIFIED = 0 - HUMAN_AGENT = 1 - AUTOMATED_AGENT = 2 - END_USER = 3 - - dialogflow_participant = proto.Field(proto.STRING, number=1,) - role = proto.Field( - proto.ENUM, - number=2, - enum="Conversation.Transcript.TranscriptSegment.Participant.Role", - ) - text = proto.Field(proto.STRING, number=1,) confidence = proto.Field(proto.FLOAT, number=2,) words = proto.RepeatedField( @@ -242,10 +218,8 @@ class Role(proto.Enum): ) language_code = proto.Field(proto.STRING, number=4,) channel_tag = proto.Field(proto.INT32, number=5,) - participant = proto.Field( - proto.MESSAGE, - number=8, - message="Conversation.Transcript.TranscriptSegment.Participant", + segment_participant = proto.Field( + proto.MESSAGE, number=9, message="ConversationParticipant", ) transcript_segments = proto.RepeatedField( @@ -532,10 +506,6 @@ class AnnotationBoundary(proto.Message): an annotation. Attributes: - time_offset (google.protobuf.duration_pb2.Duration): - Deprecated: Use ``word_index`` for the detailed boundary. - The time offset of this boundary with respect to the start - time of the first word in the transcript piece. word_index (int): The word index of this boundary with respect to the first word in the transcript piece. This @@ -546,12 +516,6 @@ class AnnotationBoundary(proto.Message): located. This index starts at zero. """ - time_offset = proto.Field( - proto.MESSAGE, - number=2, - oneof="detailed_boundary", - message=duration_pb2.Duration, - ) word_index = proto.Field(proto.INT32, number=3, oneof="detailed_boundary",) transcript_index = proto.Field(proto.INT32, number=1,) @@ -866,6 +830,10 @@ class PhraseMatcher(proto.Message): activation_update_time (google.protobuf.timestamp_pb2.Timestamp): Output only. The most recent time at which the activation status was updated. + role_match (google.cloud.contact_center_insights_v1.types.ConversationParticipant.Role): + The role whose utterances the phrase matcher should be + matched against. If the role is ROLE_UNSPECIFIED it will be + matched against any utterances in the transcript. """ class PhraseMatcherType(proto.Enum): @@ -891,6 +859,9 @@ class PhraseMatcherType(proto.Enum): activation_update_time = proto.Field( proto.MESSAGE, number=9, message=timestamp_pb2.Timestamp, ) + role_match = proto.Field( + proto.ENUM, number=10, enum="ConversationParticipant.Role", + ) class PhraseMatchRuleGroup(proto.Message): @@ -1253,4 +1224,28 @@ class DialogflowInteractionData(proto.Message): confidence = proto.Field(proto.FLOAT, number=2,) +class ConversationParticipant(proto.Message): + r"""The call participant speaking for a given utterance. + + Attributes: + dialogflow_participant (str): + The name of the Dialogflow participant. + Format: + projects/{project}/locations/{location}/conversations/{conversation}/participants/{participant} + role (google.cloud.contact_center_insights_v1.types.ConversationParticipant.Role): + The role of the participant. + """ + + class Role(proto.Enum): + r"""The role of the participant.""" + ROLE_UNSPECIFIED = 0 + HUMAN_AGENT = 1 + AUTOMATED_AGENT = 2 + END_USER = 3 + ANY_AGENT = 4 + + dialogflow_participant = proto.Field(proto.STRING, number=1,) + role = proto.Field(proto.ENUM, number=2, enum=Role,) + + __all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/tests/unit/gapic/contact_center_insights_v1/test_contact_center_insights.py b/tests/unit/gapic/contact_center_insights_v1/test_contact_center_insights.py index 227b405..d757a83 100644 --- a/tests/unit/gapic/contact_center_insights_v1/test_contact_center_insights.py +++ b/tests/unit/gapic/contact_center_insights_v1/test_contact_center_insights.py @@ -135,18 +135,6 @@ def test_contact_center_insights_client_from_service_account_info(client_class): assert client.transport._host == "contactcenterinsights.googleapis.com:443" -@pytest.mark.parametrize( - "client_class", [ContactCenterInsightsClient, ContactCenterInsightsAsyncClient,] -) -def test_contact_center_insights_client_service_account_always_use_jwt(client_class): - with mock.patch.object( - service_account.Credentials, "with_always_use_jwt_access", create=True - ) as use_jwt: - creds = service_account.Credentials(None, None, None) - client = client_class(credentials=creds) - use_jwt.assert_not_called() - - @pytest.mark.parametrize( "transport_class,transport_name", [ @@ -154,7 +142,7 @@ def test_contact_center_insights_client_service_account_always_use_jwt(client_cl (transports.ContactCenterInsightsGrpcAsyncIOTransport, "grpc_asyncio"), ], ) -def test_contact_center_insights_client_service_account_always_use_jwt_true( +def test_contact_center_insights_client_service_account_always_use_jwt( transport_class, transport_name ): with mock.patch.object( @@ -164,6 +152,13 @@ def test_contact_center_insights_client_service_account_always_use_jwt_true( transport = transport_class(credentials=creds, always_use_jwt_access=True) use_jwt.assert_called_once_with(True) + with mock.patch.object( + service_account.Credentials, "with_always_use_jwt_access", create=True + ) as use_jwt: + creds = service_account.Credentials(None, None, None) + transport = transport_class(credentials=creds, always_use_jwt_access=False) + use_jwt.assert_not_called() + @pytest.mark.parametrize( "client_class", [ContactCenterInsightsClient, ContactCenterInsightsAsyncClient,] @@ -248,6 +243,7 @@ def test_contact_center_insights_client_client_options( client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, ) # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is @@ -264,6 +260,7 @@ def test_contact_center_insights_client_client_options( client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, ) # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is @@ -280,6 +277,7 @@ def test_contact_center_insights_client_client_options( client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, ) # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has @@ -308,6 +306,7 @@ def test_contact_center_insights_client_client_options( client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, ) @@ -384,6 +383,7 @@ def test_contact_center_insights_client_mtls_env_auto( client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, ) # Check the case ADC client cert is provided. Whether client cert is used depends on @@ -417,6 +417,7 @@ def test_contact_center_insights_client_mtls_env_auto( client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, ) # Check the case client_cert_source and ADC client cert are not provided. @@ -438,6 +439,7 @@ def test_contact_center_insights_client_mtls_env_auto( client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, ) @@ -472,6 +474,7 @@ def test_contact_center_insights_client_client_options_scopes( client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, ) @@ -506,6 +509,7 @@ def test_contact_center_insights_client_client_options_credentials_file( client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, ) @@ -525,6 +529,7 @@ def test_contact_center_insights_client_client_options_from_dict(): client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, ) @@ -4241,6 +4246,7 @@ def test_create_phrase_matcher( display_name="display_name_value", type_=resources.PhraseMatcher.PhraseMatcherType.ALL_OF, active=True, + role_match=resources.ConversationParticipant.Role.HUMAN_AGENT, ) response = client.create_phrase_matcher(request) @@ -4257,6 +4263,7 @@ def test_create_phrase_matcher( assert response.display_name == "display_name_value" assert response.type_ == resources.PhraseMatcher.PhraseMatcherType.ALL_OF assert response.active is True + assert response.role_match == resources.ConversationParticipant.Role.HUMAN_AGENT def test_create_phrase_matcher_from_dict(): @@ -4306,6 +4313,7 @@ async def test_create_phrase_matcher_async( display_name="display_name_value", type_=resources.PhraseMatcher.PhraseMatcherType.ALL_OF, active=True, + role_match=resources.ConversationParticipant.Role.HUMAN_AGENT, ) ) response = await client.create_phrase_matcher(request) @@ -4323,6 +4331,7 @@ async def test_create_phrase_matcher_async( assert response.display_name == "display_name_value" assert response.type_ == resources.PhraseMatcher.PhraseMatcherType.ALL_OF assert response.active is True + assert response.role_match == resources.ConversationParticipant.Role.HUMAN_AGENT @pytest.mark.asyncio @@ -4501,6 +4510,7 @@ def test_get_phrase_matcher( display_name="display_name_value", type_=resources.PhraseMatcher.PhraseMatcherType.ALL_OF, active=True, + role_match=resources.ConversationParticipant.Role.HUMAN_AGENT, ) response = client.get_phrase_matcher(request) @@ -4517,6 +4527,7 @@ def test_get_phrase_matcher( assert response.display_name == "display_name_value" assert response.type_ == resources.PhraseMatcher.PhraseMatcherType.ALL_OF assert response.active is True + assert response.role_match == resources.ConversationParticipant.Role.HUMAN_AGENT def test_get_phrase_matcher_from_dict(): @@ -4566,6 +4577,7 @@ async def test_get_phrase_matcher_async( display_name="display_name_value", type_=resources.PhraseMatcher.PhraseMatcherType.ALL_OF, active=True, + role_match=resources.ConversationParticipant.Role.HUMAN_AGENT, ) ) response = await client.get_phrase_matcher(request) @@ -4583,6 +4595,7 @@ async def test_get_phrase_matcher_async( assert response.display_name == "display_name_value" assert response.type_ == resources.PhraseMatcher.PhraseMatcherType.ALL_OF assert response.active is True + assert response.role_match == resources.ConversationParticipant.Role.HUMAN_AGENT @pytest.mark.asyncio