Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track time it takes to get a response from DevX API #875

Merged
merged 11 commits into from
Apr 1, 2021
7 changes: 5 additions & 2 deletions src/app/services/actions/permissions-action-creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export function fetchScopes(query?: IQuery): Function {

dispatch(fetchScopesPending());

const response = await fetch(permissionsUrl, options);
const response = await telemetry.trackApiCallEvent(
componentNames.FETCH_PERMISSIONS_ACTION, permissionsUrl, options);
if (response.ok) {
const scopes = await response.json();
return dispatch(fetchScopesSuccess({
Expand All @@ -72,12 +73,14 @@ export function fetchScopes(query?: IQuery): Function {
}
throw (response);
} catch (error) {
const errorMessage = error instanceof Response ?
`ApiError: ${error.status}` : `${error}`;
telemetry.trackException(
new Error(errorTypes.NETWORK_ERROR),
SeverityLevel.Error,
{
ComponentName: componentNames.FETCH_PERMISSIONS_ACTION,
Message: `${error}`
Message: errorMessage
});
return dispatch(fetchScopesError(error));
}
Expand Down
7 changes: 5 additions & 2 deletions src/app/services/actions/samples-action-creators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,22 @@ export function fetchSamples(): Function {
dispatch(fetchSamplesPending());

try {
const response = await fetch(samplesUrl, options);
const response = await telemetry.trackApiCallEvent(
componentNames.FETCH_SAMPLES_ACTION, samplesUrl, options);
if (!response.ok) {
throw response;
}
const res = await response.json();
return dispatch(fetchSamplesSuccess(res.sampleQueries));
} catch (error) {
const errorMessage = error instanceof Response ?
`ApiError: ${error.status}` : `${error}`;
telemetry.trackException(
new Error(errorTypes.NETWORK_ERROR),
SeverityLevel.Error,
{
ComponentName: componentNames.FETCH_SAMPLES_ACTION,
Message: `${error}`
Message: errorMessage
});
return dispatch(fetchSamplesError({ error }));
}
Expand Down
21 changes: 13 additions & 8 deletions src/app/services/actions/snippet-action-creator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SeverityLevel } from '@microsoft/applicationinsights-web';
import { componentNames, errorTypes, telemetry } from '../../../telemetry';
import { IAction } from '../../../types/action';
import { IRequestOptions } from '../../../types/request';
import { sanitizeQueryUrl } from '../../utils/query-url-sanitization';
import { parseSampleUrl } from '../../utils/sample-url-generation';
import { GET_SNIPPET_ERROR, GET_SNIPPET_PENDING, GET_SNIPPET_SUCCESS } from '../redux-constants';
Expand Down Expand Up @@ -41,31 +42,35 @@ export function getSnippet(language: string): Function {

dispatch(getSnippetPending());

const method = 'POST';
const headers = {
'Content-Type': 'application/http'
};
// tslint:disable-next-line: max-line-length
const body = `${sampleQuery.selectedVerb} /${queryVersion}/${requestUrl + search} HTTP/1.1\r\nHost: graph.microsoft.com\r\nContent-Type: application/json\r\n\r\n${JSON.stringify(sampleQuery.sampleBody)}`;
const options: IRequestOptions = { method, headers, body };
const obj: any = {};
const response = await fetch(snippetsUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/http'
},
body
});

const response = await telemetry.trackApiCallEvent(
componentNames.GET_SNIPPET_ACTION, snippetsUrl, options);
if (response.ok) {
const result = await response.text();
obj[language] = result;
return dispatch(getSnippetSuccess(obj));
}
throw (response);
} catch (error) {
const errorMessage = error instanceof Response ?
`ApiError: ${error.status}` : `${error}`;
const sanitizedUrl = sanitizeQueryUrl(sampleQuery.sampleUrl);
telemetry.trackException(
new Error(errorTypes.NETWORK_ERROR),
SeverityLevel.Error,
{
ComponentName: componentNames.GET_SNIPPET_ACTION,
QuerySignature: `${sampleQuery.selectedVerb} ${sanitizedUrl}`,
Message: `${error}`
Language: language,
Message: errorMessage
}
);
return dispatch(getSnippetError(error));
Expand Down
4 changes: 2 additions & 2 deletions src/app/views/query-response/snippets/snippets-helper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function Snippet(props: ISnippetProps) {
iconProps={copyIcon}
onClick={async () => {
genericCopy(snippet);
trackCopyEvent(sampleQuery, language);
trackSnippetCopyEvent(sampleQuery, language);
}}
/>
<Monaco
Expand All @@ -90,7 +90,7 @@ function Snippet(props: ISnippetProps) {
);
}

function trackCopyEvent(query: IQuery, language: string) {
function trackSnippetCopyEvent(query: IQuery, language: string) {
const sanitizedUrl = sanitizeQueryUrl(query.sampleUrl);
telemetry.trackEvent(eventTypes.BUTTON_CLICK_EVENT,
{
Expand Down
4 changes: 2 additions & 2 deletions src/app/views/query-runner/request/auth/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function Auth(props: any) {

const handleCopy = async () => {
await genericCopy(accessToken!);
trackCopyEvent();
trackTokenCopyEvent();
};

useEffect(() => {
Expand Down Expand Up @@ -70,7 +70,7 @@ export function Auth(props: any) {
</div>);
}

function trackCopyEvent() {
function trackTokenCopyEvent() {
telemetry.trackEvent(
eventTypes.BUTTON_CLICK_EVENT,
{
Expand Down
4 changes: 3 additions & 1 deletion src/modules/suggestions/suggestions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { parseOpenApiResponse } from '../../app/utils/open-api-parser';
import { componentNames, telemetry } from '../../telemetry';
import { IOpenApiParseContent, IOpenApiResponse, IParsedOpenApiResponse } from '../../types/open-api';
import { IRequestOptions } from '../../types/request';
import { getSuggestionsFromCache, storeSuggestionsInCache } from './cache-provider';
Expand Down Expand Up @@ -27,7 +28,8 @@ class Suggestions implements ISuggestions {
const options: IRequestOptions = { headers };

try {
const response = await fetch(openApiUrl, options);
const response = await telemetry.trackApiCallEvent(
componentNames.FETCH_QUERY_AUTOCOMPLETE_OPTIONS_ACTION, openApiUrl, options);
if (response.ok) {
const openApiResponse: IOpenApiResponse = await response.json();
const content: IOpenApiParseContent = {
Expand Down
4 changes: 3 additions & 1 deletion src/telemetry/ITelemetry.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { SeverityLevel } from '@microsoft/applicationinsights-web';
import { ComponentType } from 'react';
import { IQuery } from '../types/query-runner';
import { IRequestOptions } from '../types/request';

export default interface ITelemetry {
initialize(): void;
trackEvent(name: string, properties: {}): void;
trackReactComponent(Component: ComponentType, componentName?: string): ComponentType;
trackTabClickEvent(tabKey: string, sampleQuery: IQuery): void;
trackTabClickEvent(tabKey: string, sampleQuery?: IQuery): void;
trackLinkClickEvent(url: string, componentName: string): void;
trackException(error: Error, severityLevel: SeverityLevel, properties: {}): void;
trackApiCallEvent(componentName: string, url: string, options: IRequestOptions): Promise<Response>;
}
9 changes: 5 additions & 4 deletions src/telemetry/component-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
export const RUN_QUERY_BUTTON = 'Run query button';
export const THEME_CHANGE_BUTTON = 'Theme change button';
export const SELECT_THEME_BUTTON = 'Select theme button';
export const JSON_SCHEMA_COPY_BUTTON = 'JSON schema copy button';
export const RUN_HISTORY_ITEM_BUTTON = 'Run history item button';
export const SHARE_QUERY_COPY_BUTTON = 'Share query copy button';
export const ACCESS_TOKEN_COPY_BUTTON = 'Access token copy button';
Expand All @@ -13,14 +14,14 @@ export const CODE_SNIPPETS_COPY_BUTTON = 'Code snippets copy button';
export const VIEW_ALL_PERMISSIONS_BUTTON = 'View all permissions button';
export const EXPORT_HISTORY_ITEM_BUTTON = 'Export history item button';
export const DELETE_HISTORY_ITEM_BUTTON = 'Delete history item button';
export const JSON_SCHEMA_COPY_BUTTON = 'JSON schema copy button';

// List items
export const SAMPLE_QUERY_LIST_ITEM = 'Sample query list item';
export const HISTORY_LIST_ITEM = 'History list item';
export const SAMPLE_QUERY_LIST_ITEM = 'Sample query list item';

// Tabs
export const HISTORY_TAB = 'History tab';
export const JSON_SCHEMA_TAB = 'JSON schema tab';
export const SHARE_QUERY_TAB = 'Share query tab';
export const ACCESS_TOKEN_TAB = 'Access token tab';
export const REQUEST_BODY_TAB = 'Request body tab';
Expand All @@ -32,16 +33,15 @@ export const RESPONSE_PREVIEW_TAB = 'Response preview tab';
export const RESPONSE_HEADERS_TAB = 'Response headers tab';
export const TOOLKIT_COMPONENT_TAB = 'Toolkit component tab';
export const MODIFY_PERMISSIONS_TAB = 'Modify permissions tab';
export const JSON_SCHEMA_TAB = 'JSON schema tab';

// Dropdowns
export const VERSION_CHANGE_DROPDOWN = 'Version change dropdown';
export const QUERY_URL_AUTOCOMPLETE_DROPDOWN = 'Query URL autocomplete dropdown';

// Links
export const DOCUMENTATION_LINK = 'Documentation link';
export const GRAPH_TOOLKIT_PLAYGROUND_LINK = 'Graph toolkit playground link';
export const OFFICE_DEV_PROGRAM_LINK = 'Office dev program link';
export const GRAPH_TOOLKIT_PLAYGROUND_LINK = 'Graph toolkit playground link';
export const MICROSOFT_APIS_TERMS_OF_USE_LINK = 'Microsoft APIs terms of use link';
export const MICROSOFT_PRIVACY_STATEMENT_LINK = 'Microsoft privacy statement link';
export const MICROSOFT_GRAPH_API_REFERENCE_DOCS_LINK = 'Microsoft graph API reference docs link';
Expand All @@ -52,3 +52,4 @@ export const FETCH_SAMPLES_ACTION = 'Fetch samples action';
export const AUTHENTICATION_ACTION = 'Authentication action';
export const GET_ADAPTIVE_CARD_ACTION = 'Get adaptive card action';
export const FETCH_PERMISSIONS_ACTION = 'Fetch permissions action';
export const FETCH_QUERY_AUTOCOMPLETE_OPTIONS_ACTION = 'Fetch query autocomplete options action';
1 change: 1 addition & 0 deletions src/telemetry/event-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export const TAB_CLICK_EVENT = 'TAB_CLICK_EVENT';
export const LINK_CLICK_EVENT = 'LINK_CLICK_EVENT';
export const LISTITEM_CLICK_EVENT = 'LISTITEM_CLICK_EVENT';
export const DROPDOWN_CHANGE_EVENT = 'DROPDOWN_CHANGE_EVENT';
export const EXTERNAL_API_CALL_EVENT ='EXTERNAL_API_CALL_EVENT';
26 changes: 24 additions & 2 deletions src/telemetry/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { ComponentType } from 'react';
import { validateExternalLink } from '../app/utils/external-link-validation';
import { sanitizeQueryUrl } from '../app/utils/query-url-sanitization';
import { IQuery } from '../types/query-runner';
import { LINK_CLICK_EVENT, TAB_CLICK_EVENT } from './event-types';
import { IRequestOptions } from '../types/request';
import { EXTERNAL_API_CALL_EVENT, LINK_CLICK_EVENT, TAB_CLICK_EVENT } from './event-types';
import ITelemetry from './ITelemetry';

class Telemetry implements ITelemetry {
Expand Down Expand Up @@ -47,7 +48,7 @@ class Telemetry implements ITelemetry {
return withAITracking(this.reactPlugin, ComponentToTrack, componentName);
}

public trackTabClickEvent(tabKey: string, sampleQuery: IQuery | null = null) {
public trackTabClickEvent(tabKey: string, sampleQuery?: IQuery) {
let componentName = tabKey.replace('-', ' ');
componentName = `${componentName.charAt(0).toUpperCase()}${componentName.slice(1)} tab`;
const properties: { [key: string]: any } = {
Expand All @@ -65,6 +66,27 @@ class Telemetry implements ITelemetry {
validateExternalLink(url, componentName);
}

public async trackApiCallEvent(componentName: string, url: string,
options: IRequestOptions): Promise<Response> {
const properties: { [key: string]: string } = {
ComponentName: componentName,
IsRequestSuccessful: 'false',
Url: url
millicentachieng marked this conversation as resolved.
Show resolved Hide resolved
}
try {
this.appInsights.startTrackEvent(EXTERNAL_API_CALL_EVENT);
const response = await fetch(url, options);
properties.HttpStatusCode = `${response.status}`;
if (response.ok) {
properties.IsRequestSuccessful = 'true';
}
return response;
}
finally {
this.appInsights.stopTrackEvent(EXTERNAL_API_CALL_EVENT, properties);
}
}

private filterFunction(envelope: ITelemetryItem) {
const telemetryItem = envelope.baseData || {};
telemetryItem.properties = telemetryItem.properties || {};
Expand Down