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

Added request script logs #2041

Merged
merged 4 commits into from Nov 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 DEV.md
@@ -20,3 +20,8 @@ For this error, you need to your Google Cloud Console and add `Service Account T

https://dev.to/wceolin/permission-error-when-generating-a-custom-token-on-cloud-functions-1e6c
https://firebase.google.com/docs/auth/admin/create-custom-tokens#service_account_does_not_have_required_permissions

### Something weird is broken, with no obvious way to fix
Delete all node_modules
Remove node_modules/.cache/nx
Remove angular cache (packages/altair-app/.angular)
@@ -40,11 +40,13 @@ exports[`QueryEditorComponent should render correctly 1`] = `
</nz-tab>

<nz-tab>

<app-pre-request-editor />

</nz-tab>

<nz-tab>

<app-post-request-editor />

</nz-tab>
@@ -84,14 +84,24 @@
</div>
</div>
</nz-tab>
<nz-tab [nzTitle]="'PRE_REQUEST_TAB' | translate">
<nz-tab [nzTitle]="preRequestTitleTemplate">
<ng-template #preRequestTitleTemplate>
<span [ngClass]="{'ant-tabs--status-enabled': preRequest.enabled}">
{{ 'PRE_REQUEST_TAB' | translate }}
</span>
</ng-template>
<app-pre-request-editor
[preRequest]="preRequest"
(preRequestScriptChange)="preRequestScriptChange.emit($event)"
(preRequestEnabledChange)="preRequestEnabledChange.emit($event)"
></app-pre-request-editor>
</nz-tab>
<nz-tab [nzTitle]="'POST_REQUEST_TAB' | translate">
<nz-tab [nzTitle]="postRequestTitleTemplate">
<ng-template #postRequestTitleTemplate>
<span [ngClass]="{'ant-tabs--status-enabled': postRequest.enabled}">
{{ 'POST_REQUEST_TAB' | translate }}
</span>
</ng-template>
<app-post-request-editor
[postRequest]="postRequest"
(postRequestScriptChange)="postRequestScriptChange.emit($event)"
@@ -63,18 +63,13 @@
[fullHeight]="true"
[extensions]="editorExtensions"
></app-codemirror>
<!-- <ngx-codemirror
#editor
class="app-query-result-textarea"
[options]="resultEditorConfig"
[ngModel]="queryResult | json"
></ngx-codemirror> -->
</div>
</div>
</div>
</nz-tab>
<nz-tab
[nzTitle]="'SUBSCRIPTION_RESULT_TAB' | translate"
*ngIf="!(!isSubscribed && !subscriptionResponses?.length)"
[nzDisabled]="!isSubscribed && !subscriptionResponses?.length"
>
<div class="query-result-container">
@@ -117,6 +112,21 @@
</div>
</div>
</nz-tab>
<nz-tab
[nzTitle]="'REQUEST_SCRIPT_LOGS_TAB' | translate"
*ngIf="!(isSubscribed || !requestScriptLogs?.length)"
[nzDisabled]="isSubscribed || !requestScriptLogs?.length"
>
<div class="request-script-logs">
<div class="request-script-logs__list">
<div class="request-script-logs__line" *ngFor="let item of requestScriptLogs">
<div class="request-script-logs__line-time">{{ item.time | date: 'mediumTime' }}</div>
<!-- <div class="request-script-logs__line-source">{{ item.source }}</div> -->
<div class="request-script-logs__line-text">{{ item.text }}</div>
</div>
</div>
</div>
</nz-tab>
<nz-tab
[nzTitle]="'RESPONSE_HEADERS_TAB' | translate"
>
@@ -11,7 +11,10 @@ import {
} from '@angular/core';

import isElectron from 'altair-graphql-core/build/utils/is_electron';
import { SubscriptionResponse } from 'altair-graphql-core/build/types/state/query.interfaces';
import {
LogLine,
SubscriptionResponse,
} from 'altair-graphql-core/build/types/state/query.interfaces';
import { AltairPanel } from 'altair-graphql-core/build/plugin/panel';
import { TrackByIdItem } from '../../interfaces/shared';
import { EditorState, Extension } from '@codemirror/state';
@@ -29,6 +32,7 @@ export class QueryResultComponent implements OnChanges {
@Input() responseStatus = 0;
@Input() responseStatusText = '';
@Input() responseHeaders = {};
@Input() requestScriptLogs: LogLine[] = [];
@Input() isSubscribed = false;
@Input() subscriptionResponses: SubscriptionResponse[] = [];
@Input() subscriptionUrl = '';
@@ -61,8 +65,6 @@ export class QueryResultComponent implements OnChanges {
EditorState.tabSize.of(this.tabSize),
];

constructor() {}

ngOnChanges(changes: SimpleChanges) {
if (changes?.subscriptionResponses?.currentValue) {
const scrollTopTimeout = setTimeout(() => {
@@ -69,6 +69,7 @@
[responseStatus]="responseStatus$ | async"
[responseStatusText]="responseStatusText$ | async"
[responseHeaders]="responseHeaders$ | async"
[requestScriptLogs]="requestScriptLogs$ | async"
[isSubscribed]="isSubscribed$ | async"
[subscriptionResponses]="subscriptionResponses$ | async"
[subscriptionUrl]="subscriptionUrl"
@@ -42,6 +42,7 @@ import { fadeInOutAnimationTrigger } from '../../animations';
import { IDictionary, TrackByIdItem } from '../../interfaces/shared';
import collectVariables from 'codemirror-graphql/utils/collectVariables';
import {
LogLine,
QueryEditorState,
QueryState,
SelectedOperation,
@@ -86,6 +87,7 @@ export class WindowComponent implements OnInit {
responseHeaders$: Observable<IDictionary | undefined>;
isSubscribed$: Observable<boolean>;
subscriptionResponses$: Observable<SubscriptionResponse[]>;
requestScriptLogs$: Observable<LogLine[]>;
selectedOperation$?: Observable<SelectedOperation>;
queryOperations$: Observable<any[]>;
streamState$: Observable<'connected' | 'failed' | 'uncertain' | ''>;
@@ -200,6 +202,9 @@ export class WindowComponent implements OnInit {
this.subscriptionResponses$ = this.getWindowState().pipe(
select(fromRoot.getSubscriptionResponses)
);
this.requestScriptLogs$ = this.getWindowState().pipe(
select(fromRoot.getRequestScriptLogs)
);
this.autoscrollSubscriptionResponses$ = this.getWindowState().pipe(
select(fromRoot.getAutoscrollSubscriptionResponse)
);
@@ -20,8 +20,6 @@ import { Injectable } from '@angular/core';
import { Store, Action } from '@ngrx/store';
import { Actions, ofType, createEffect } from '@ngrx/effects';

const validUrl = require('valid-url');

import {
GqlService,
NotifyService,
@@ -51,6 +49,7 @@ import {
downloadData,
copyToClipboard,
openFile,
isValidUrl,
} from '../utils';
import { debug } from '../utils/logger';
import { generateCurl } from '../utils/curl';
@@ -137,6 +136,7 @@ export class QueryEffects {
return EMPTY;
}

const preRequestScriptLogs = transformedData?.requestScriptLogs;
const { url, variables, headers, query } =
this.queryService.hydrateAllHydratables(
response.data,
@@ -145,7 +145,7 @@ export class QueryEffects {
let selectedOperation = response.data.query.selectedOperation;

// If the URL is not set or is invalid, just return
if (!url || !validUrl.isUri(url)) {
if (!url || !isValidUrl(url)) {
this.notifyService.error('The URL is invalid!');
this.store.dispatch(
new layoutActions.StopLoadingAction(response.windowId)
@@ -301,15 +301,13 @@ export class QueryEffects {
}
requestStatusCode = res.data.response.status;
requestStatusText = res.data.response.statusText;
return res.data;
return res;
}),
map((result) => {
const responseBody = result?.response.body
? { ...result?.response.body }
: result?.response.body;
const responseBody = result?.data?.response.body;

if (
responseBody &&
responseBody?.extensions &&
response.state.settings['response.hideExtensions']
) {
Reflect.deleteProperty(responseBody, 'extensions');
@@ -321,10 +319,19 @@ export class QueryEffects {
response.windowId
)
);
this.store.dispatch(
new queryActions.SetRequestScriptLogsAction(
response.windowId,
[
...(preRequestScriptLogs || []),
...(result?.transformedData?.requestScriptLogs || []),
]
)
);
this.store.dispatch(
new queryActions.SetQueryResultResponseHeadersAction(
response.windowId,
{ headers: result?.meta.headers }
{ headers: result?.data?.meta.headers }
)
);
return result;
@@ -361,9 +368,15 @@ export class QueryEffects {
response.windowId,
{
responseStatus: requestStatusCode,
responseTime: res ? res.meta.responseTime : 0,
requestStartTime: res ? res.meta.requestStartTime : 0,
requestEndTime: res ? res.meta.requestEndTime : 0,
responseTime: res?.data
? res.data.meta.responseTime
: 0,
requestStartTime: res?.data
? res.data.meta.requestStartTime
: 0,
requestEndTime: res?.data
? res.data.meta.requestEndTime
: 0,
responseStatusText: requestStatusText,
}
)
@@ -397,7 +410,7 @@ export class QueryEffects {
switchMap((data: queryActions.Action) => {
const url = this.environmentService.hydrate(data.payload.url);
// If the URL is not valid
if (!validUrl.isUri(url)) {
if (!isValidUrl(url)) {
this.notifyService.error('The URL is invalid!');
} else {
this.notifyService.success('URL has been set.');
@@ -1,9 +1,10 @@
export class RequestScriptError extends Error {
cause: Error;

constructor(error: Error) {
super(error.message);
constructor(error: Error | string) {
const message = error instanceof Error ? error.message : error;
super(message);
this.name = 'RequestScriptError';
this.cause = error;
this.cause = error instanceof Error ? error : new Error(error);
}
}
@@ -15,6 +15,7 @@ import { SendRequestResponse } from '../gql/gql.service';
import { HeaderState } from 'altair-graphql-core/build/types/state/header.interfaces';
import { RootState } from 'altair-graphql-core/build/types/state/state.interfaces';
import { RequestScriptError } from './errors';
import { LogLine } from 'altair-graphql-core/build/types/state/query.interfaces';

export enum RequestType {
INTROSPECTION = 'introspection',
@@ -33,6 +34,7 @@ export interface ScriptContextData {
variables: string;
query: string;
environment: IDictionary;
requestScriptLogs?: LogLine[];
response?: SendRequestResponse;
requestType?: RequestType;
__toSetActiveEnvironment?: IDictionary;
@@ -49,6 +51,7 @@ interface GlobalHelperContext {
data: ScriptContextData;
helpers: ScriptContextHelpers;
importModule: (moduleName: string) => any;
log: (d: any) => void;
response?: ScriptContextResponse;
}

@@ -102,6 +105,7 @@ export class PreRequestService {
});
interpreter.import({
altair: this.getGlobalContext(clonedMutableData),
alert: (msg: string) => this.notifyService.info(`Alert: ${msg}`),
});
interpreter.run(`
const program = async() => {
@@ -129,9 +133,15 @@ export class PreRequestService {
...JSON.parse(activeEnvState.variablesJson),
...clonedMutableData.__toSetActiveEnvironment,
};
const activeEnvStateId = activeEnvState.id;

if (!activeEnvStateId) {
throw new RequestScriptError('Invalid active environment state ID');
}

this.store.dispatch(
new environmentsActions.UpdateSubEnvironmentJsonAction({
id: activeEnvState.id!,
id: activeEnvStateId,
value: JSON.stringify(envVariables, null, 2),
})
);
@@ -192,12 +202,22 @@ export class PreRequestService {
},
importModule: (moduleName: string) => this.importModuleHelper(moduleName),
response: this.buildContextResponse(data),
log: (d: any) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@imolorhe just curious, but since this log override is in pre-request.service.ts, does a similar change need to be make to post-request.service.ts?

The post-request tab is where personally I'm doing "post process & console.log".

That said, I'm just naively scanning the PR, so if the post-request tab ends up getting this functionality as well, then nevermind/np! Thanks!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a preview link in the PR. You can always test the change there 🙂 However, we don't have a post-request.service.ts. We just have the pre-request.service.ts which powers both. I just haven't renamed it since post requests were introduced.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Just tested it and it looks great!

data.requestScriptLogs = data.requestScriptLogs || [];
data.requestScriptLogs.push({
time: Date.now(),
text: JSON.stringify(d, null, 2),
source: 'Request script',
});
},
};
}

async importModuleHelper(moduleName: string) {
if (!Object.keys(ModuleImports).includes(moduleName)) {
throw new Error(`No pre request module found matching "${moduleName}"`);
throw new Error(
`No request script module found matching "${moduleName}"`
);
}

return ModuleImports[moduleName].exec();
@@ -1,6 +1,9 @@
import { Action as NGRXAction } from '@ngrx/store';
import { SubscriptionProvider } from 'altair-graphql-core/build/subscriptions/subscription-provider';
import { QueryEditorState } from 'altair-graphql-core/build/types/state/query.interfaces';
import {
LogLine,
QueryEditorState,
} from 'altair-graphql-core/build/types/state/query.interfaces';
import { IDictionary } from '../../interfaces/shared';

export const SET_URL = 'SET_URL';
@@ -52,6 +55,8 @@ export const SHOW_EDITOR_ALERT = 'SHOW_EDITOR_ALERT';
export const SET_QUERY_OPERATIONS = 'SET_QUERY_OPERATIONS';
export const SET_QUERY_EDITOR_STATE = 'SET_QUERY_EDITOR_STATE';

export const SET_REQUEST_SCRIPT_LOGS = 'SET_REQUEST_SCRIPT_LOGS';

export class SetUrlAction implements NGRXAction {
readonly type = SET_URL;

@@ -266,6 +271,12 @@ export class SetQueryEditorStateAction implements NGRXAction {
constructor(public windowId: string, public payload: QueryEditorState) {}
}

export class SetRequestScriptLogsAction implements NGRXAction {
readonly type = SET_REQUEST_SCRIPT_LOGS;

constructor(public windowId: string, public payload: LogLine[]) {}
}

export type Action =
| SetUrlAction
| SetUrlFromDbAction
@@ -296,4 +307,5 @@ export type Action =
| CancelQueryRequestAction
| SetHTTPMethodAction
| SetQueryOperationsAction
| SetQueryEditorStateAction;
| SetQueryEditorStateAction
| SetRequestScriptLogsAction;