Skip to content

Commit

Permalink
refactor(RESTServices): migrate code to TypeScript (#5453)
Browse files Browse the repository at this point in the history
### 💭 Notes
Also includes one style fix (for `z-index` issue; not perfect, but
didn't want to spend too much time on it). The bug was that if you had
multiple REST Service Hooks registered, the action buttons (their
tooltips) on the right side were being covered by other row.

### 👀 Preview steps
Steps for testing:
1. ℹ️ have account and a deployed project
2. go to Project --> Settings --> RESTServices
3. create a new service (use all the options; use some fake url)
4. 🟢 notice that all the possible options in the form works as
previously (as expected)
5. create few submissions for your project
6. go back to Project --> Settings --> RESTServices
7. 🟢 notice that your registered service will have some count of Failed
8. click on service name
9. 🟢 notice a list of logs, all should be Failed (because fake url)
10. try using all three buttons: retry all, retry, and information
11. 🟢 all options should work as previously (retries cause a call and
"Pending" status; information opens modal)

Steps for working url:
1. ℹ️ have account and a deployed project
2. go to Project --> Settings --> RESTServices
3. create a new service (use all the options; use some mock url, e.g.
https://mockapi.io or https://beeceptor.com/)
5. create few submissions for your project
6. go back to Project --> Settings --> RESTServices
7. 🟢 notice that your registered service will have some count of Success
8. click on service name
9. 🟢 notice a list of logs, all should be Success (because working mock
url)
10. 🟢 notice there are no buttons
11. click on one of the logs
12. 🟢 notice a Submission Modal opens
  • Loading branch information
magicznyleszek authored Feb 3, 2025
1 parent c6ecbf6 commit deb4e0a
Show file tree
Hide file tree
Showing 8 changed files with 387 additions and 223 deletions.
35 changes: 34 additions & 1 deletion jsapp/js/actions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,31 @@ interface ReportsSetCustomCompletedDefinition extends Function {
listen: (callback: (response: AssetResponse, crid: string) => void) => Function;
}

interface HooksGetLogsDefinition extends Function {
(
assetUid: string,
hookUid: string,
options: {
onComplete: (data: PaginatedResponse<HookResponse>) => void;
onFail: () => void;
}
): void;
listen: (callback: (
assetUid: string,
hookUid: string,
options: {
onComplete: (data: PaginatedResponse<HookResponse>) => void;
onFail: () => void;
}
) => void) => Function;
completed: HooksGetLogsCompletedDefinition;
failed: GenericFailedDefinition;
}
interface HooksGetLogsCompletedDefinition extends Function {
(response: PaginatedResponse<HookResponse>): void;
listen: (callback: (response: PaginatedResponse<HookResponse>) => void) => Function;
}

// NOTE: as you use more actions in your ts files, please extend this namespace,
// for now we are defining only the ones we need.
export namespace actions {
Expand Down Expand Up @@ -226,7 +251,15 @@ export namespace actions {
refreshTableSubmissions: GenericDefinition;
getAssetFiles: GenericDefinition;
};
const hooks: object;
const hooks: {
add: GenericDefinition;
update: GenericDefinition;
delete: GenericDefinition;
getAll: GenericDefinition;
getLogs: HooksGetLogsDefinition;
retryLog: GenericDefinition;
retryLogs: GenericDefinition;
};
const misc: {
getUser: GetUserDefinition;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
// Libraries
import React from 'react';
import autoBind from 'react-autobind';
import reactMixin from 'react-mixin';
import Reflux from 'reflux';
import alertify from 'alertifyjs';
import pageState from 'js/pageState.store';
import bem from 'js/bem';

// Partial components
import LoadingSpinner from 'js/components/common/loadingSpinner';
import {actions} from '../../actions';
import mixins from '../../mixins';
import {dataInterface} from '../../dataInterface';
import {formatTime, notify} from 'utils';
import Button from 'js/components/common/button';

// Stores, hooks and utilities
import pageState from 'js/pageState.store';
import {actions} from 'js/actions';
import {dataInterface} from 'js/dataInterface';
import {formatTime, notify} from 'js/utils';
import assetStore from 'jsapp/js/assetStore';
import {getRouteAssetUid} from 'jsapp/js/router/routerUtils';

// Constants and types
import type {
FailResponse,
ExternalServiceLogResponse,
PaginatedResponse,
ExternalServiceHookResponse,
RetryExternalServiceLogsResponse,
} from 'js/dataInterface';
import {
HOOK_LOG_STATUSES,
MODAL_TYPES
} from '../../constants';
import Button from 'js/components/common/button';
MODAL_TYPES,
} from 'js/constants';

interface RESTServiceLogsProps {
assetUid: string;
hookUid: string;
}

export default class RESTServiceLogs extends React.Component {
constructor(props){
interface RESTServiceLogsState {
hookName: string | null;
isHookActive: boolean;
assetUid: string;
hookUid: string;
isLoadingHook: boolean;
isLoadingLogs: boolean;
logs: ExternalServiceLogResponse[];
totalLogsCount?: number;
nextPageUrl: string | null;
}

export default class RESTServiceLogs extends React.Component<RESTServiceLogsProps, RESTServiceLogsState> {
constructor(props: RESTServiceLogsProps) {
super(props);
this.state = {
hookName: null,
Expand All @@ -27,29 +56,23 @@ export default class RESTServiceLogs extends React.Component {
isLoadingHook: true,
isLoadingLogs: true,
logs: [],
nextPageUrl: null
nextPageUrl: null,
};
autoBind(this);
}

componentDidMount() {
this.listenTo(
actions.hooks.getLogs.completed,
this.onLogsUpdated
);
actions.hooks.getLogs.completed.listen(this.onLogsUpdated.bind(this));

dataInterface.getHook(this.state.assetUid, this.state.hookUid)
.done((data) => {
.done((data: ExternalServiceHookResponse) => {
this.setState({
isLoadingHook: false,
hookName: data.name,
isHookActive: data.active
isHookActive: data.active,
});
})
.fail(() => {
this.setState({
isLoadingHook: false
});
this.setState({isLoadingHook: false});
notify.error(t('Could not load REST Service'));
});

Expand All @@ -62,30 +85,33 @@ export default class RESTServiceLogs extends React.Component {
isLoadingLogs: false,
logs: data.results,
nextPageUrl: data.next,
totalLogsCount: data.count
totalLogsCount: data.count,
});
},
onFail: () => {
this.setState({
isLoadingLogs: false
});
this.setState({isLoadingLogs: false});
notify.error(t('Could not load REST Service logs'));
}
},
}
);
}

loadMore() {
this.setState({isLoadingLogs: false});

dataInterface.loadNextPageUrl(this.state.nextPageUrl)
if (this.state.nextPageUrl === null) {
return;
}

(dataInterface.loadNextPageUrl(this.state.nextPageUrl) as JQuery.jqXHR<PaginatedResponse<ExternalServiceLogResponse>>)
.done((data) => {
const newLogs = [].concat(this.state.logs, data.results);
let newLogs: ExternalServiceLogResponse[] = [];
newLogs = newLogs.concat(this.state.logs, data.results);
this.setState({
isLoadingLogs: false,
logs: newLogs,
nextPageUrl: data.next,
totalLogsCount: data.count
totalLogsCount: data.count,
});
})
.fail(() => {
Expand All @@ -94,12 +120,12 @@ export default class RESTServiceLogs extends React.Component {
});
}

onLogsUpdated(data) {
onLogsUpdated(data: PaginatedResponse<ExternalServiceLogResponse>) {
this.setState({logs: data.results});
}

retryAll() {
const failedLogUids = [];
const failedLogUids: string[] = [];
this.state.logs.forEach((log) => {
if (log.status === HOOK_LOG_STATUSES.FAILED) {
failedLogUids.push(log.uid);
Expand All @@ -111,14 +137,14 @@ export default class RESTServiceLogs extends React.Component {
this.state.assetUid,
this.state.hookUid,
{
onComplete: (response) => {
onComplete: (response: RetryExternalServiceLogsResponse) => {
this.overrideLogsStatus(response.pending_uids, HOOK_LOG_STATUSES.PENDING);
}
},
}
);
}

retryLog(log) {
retryLog(log: ExternalServiceLogResponse) {
// make sure to allow only retrying failed logs
if (log.status !== HOOK_LOG_STATUSES.FAILED) {
return;
Expand All @@ -130,56 +156,55 @@ export default class RESTServiceLogs extends React.Component {
this.state.assetUid,
this.state.hookUid,
log.uid, {
onFail: (response) => {
if (response.responseJSON && response.responseJSON.detail) {
onFail: (response: FailResponse) => {
if (response.responseJSON?.detail) {
this.overrideLogMessage(log.uid, response.responseJSON.detail);
}
this.overrideLogsStatus([log.uid], HOOK_LOG_STATUSES.FAILED);
}
},
}
);
}

overrideLogMessage(logUid, newMessage) {
overrideLogMessage(logUid: string, newMessage: string) {
const currentLogs = this.state.logs;
currentLogs.forEach((currentLog) => {
if (currentLog.uid === logUid) {
currentLog.message = newMessage;
}
});
this.setState({
logs: currentLogs
});
this.setState({logs: currentLogs});
}

// useful to mark logs as pending, before BE tells about it
// NOTE: logUids is an array
overrideLogsStatus(logUids, newStatus) {
overrideLogsStatus(logUids: string[], newStatus: number) {
const currentLogs = this.state.logs;
currentLogs.forEach((currentLog) => {
if (logUids.includes(currentLog.uid)) {
currentLog.status = newStatus;
}
});
this.setState({
logs: currentLogs
});
this.setState({logs: currentLogs});
}

showLogInfo(log) {
const title = t('Submission Failure Detail (##id##)').replace('##id##', log.submission_id);
showLogInfo(log: ExternalServiceLogResponse) {
const title = t('Submission Failure Detail (##id##)').replace('##id##', String(log.submission_id));
const escapedMessage = $('<div/>').text(log.message).html();
alertify.alert(title, `<pre>${escapedMessage}</pre>`);
}

openSubmissionModal(log) {
const currentAsset = this.currentAsset();
pageState.switchModal({
type: MODAL_TYPES.SUBMISSION,
sid: log.submission_id,
asset: currentAsset,
ids: [log.submission_id]
});
openSubmissionModal(log: ExternalServiceLogResponse) {
const currentAssetUid = getRouteAssetUid();
if (currentAssetUid !== null) {
const currentAsset = assetStore.getAsset(currentAssetUid);
pageState.switchModal({
type: MODAL_TYPES.SUBMISSION,
sid: log.submission_id,
asset: currentAsset,
ids: [log.submission_id],
});
}
}

hasAnyFailedLogs() {
Expand All @@ -192,7 +217,7 @@ export default class RESTServiceLogs extends React.Component {
return hasAny;
}

hasInfoToDisplay(log) {
hasInfoToDisplay(log: ExternalServiceLogResponse) {
return log.status !== HOOK_LOG_STATUSES.SUCCESS && log.message.length > 0;
}

Expand Down Expand Up @@ -278,9 +303,7 @@ export default class RESTServiceLogs extends React.Component {
</bem.ServiceRow>

{this.state.logs.map((log, n) => {
const rowProps = {
key: n
};
const rowProps: {m?: string; onClick?: () => void} = {};
let statusMod = '';
let statusLabel = '';
if (log.status === HOOK_LOG_STATUSES.SUCCESS) {
Expand All @@ -294,7 +317,7 @@ export default class RESTServiceLogs extends React.Component {
statusLabel = t('Pending');

if (log.tries && log.tries > 1) {
statusLabel = t('Pending (##count##×)').replace('##count##', log.tries);
statusLabel = t('Pending (##count##×)').replace('##count##', String(log.tries));
}
}
if (log.status === HOOK_LOG_STATUSES.FAILED) {
Expand All @@ -303,7 +326,7 @@ export default class RESTServiceLogs extends React.Component {
}

return (
<bem.ServiceRow {...rowProps}>
<bem.ServiceRow {...rowProps} key={n}>
<bem.ServiceRow__column m='submission'>
{log.submission_id}
</bem.ServiceRow__column>
Expand Down Expand Up @@ -359,6 +382,3 @@ export default class RESTServiceLogs extends React.Component {
}
}
}

reactMixin(RESTServiceLogs.prototype, Reflux.ListenerMixin);
reactMixin(RESTServiceLogs.prototype, mixins.contextRouter);
File renamed without changes.
Loading

0 comments on commit deb4e0a

Please sign in to comment.