Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ test_logs

# AI tools
.claude
.worktrees
24 changes: 17 additions & 7 deletions src/components/Autocomplete/Autocomplete.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,7 @@ export default class Autocomplete extends Component {

onClick(e) {
const userInput = e.currentTarget.innerText;
if (this.props.strict) {
this.props.onChange && this.props.onChange(userInput);
}
this.props.onChange && this.props.onChange(userInput);
const label = this.props.label || this.props.buildLabel(userInput);

this.inputRef.current.focus();
Expand Down Expand Up @@ -252,15 +250,20 @@ export default class Autocomplete extends Component {
const { userInput } = this.state;

if (e.keyCode === 13 || e.key === 'Enter') {
if (userInput && userInput.length > 0 && this.props.onSubmit) {
this.props.onSubmit(userInput);
const resolvedInput = filteredSuggestions[activeSuggestion] || userInput;

if (resolvedInput && resolvedInput.length > 0) {
this.props.onChange && this.props.onChange(resolvedInput);
if (this.props.onSubmit) {
this.props.onSubmit(resolvedInput);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

this.setState({
active: true,
activeSuggestion: 0,
showSuggestions: false,
userInput: filteredSuggestions[activeSuggestion] || userInput,
userInput: resolvedInput,
});
} else if (e.keyCode === 9) {
// Tab
Expand Down Expand Up @@ -343,13 +346,20 @@ export default class Autocomplete extends Component {

let suggestionsListComponent;
if (showSuggestions && !hidden && filteredSuggestions.length) {
const containerWidth = this.fieldRef.current
? this.fieldRef.current.offsetWidth
: undefined;
const mergedSuggestionsStyle = {
...suggestionsStyle,
...(containerWidth ? { width: containerWidth + 'px' } : {}),
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
suggestionsListComponent = (
<SuggestionsList
position={this.state.position}
ref={this.dropdownRef}
onExternalClick={onExternalClick}
suggestions={filteredSuggestions}
suggestionsStyle={suggestionsStyle}
suggestionsStyle={mergedSuggestionsStyle}
suggestionsItemStyle={suggestionsItemStyle}
activeSuggestion={activeSuggestion}
onClick={onClick}
Expand Down
88 changes: 85 additions & 3 deletions src/dashboard/Data/ApiConsole/RestConsole.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
import Autocomplete from 'components/Autocomplete/Autocomplete.react';
import Button from 'components/Button/Button.react';
import Dropdown from 'components/Dropdown/Dropdown.react';
import Field from 'components/Field/Field.react';
Expand All @@ -26,6 +27,29 @@ import Toggle from 'components/Toggle/Toggle.react';
import Toolbar from 'components/Toolbar/Toolbar.react';
import { CurrentApp } from 'context/currentApp';

const PARSE_API_ENDPOINTS = [
'batch',
'classes/',
'users',
'login',
'logout',
'sessions',
'roles',
'files/',
'events/',
'push',
'installations',
'functions/',
'jobs/',
'schemas/',
'config',
'hooks/functions',
'hooks/triggers',
'aggregate/',
'purge/',
'health',
];

export default class RestConsole extends Component {
static contextType = CurrentApp;
constructor() {
Expand All @@ -43,9 +67,40 @@ export default class RestConsole extends Component {
inProgress: false,
error: false,
curlModal: false,
classNames: [],
};
}

componentDidMount() {
this.context
.apiRequest('GET', 'schemas', {}, { useMasterKey: true })
.then(({ results }) => {
if (results) {
this.setState({ classNames: results.map(s => s.className) });
}
})
.catch(() => {});
}

buildEndpointSuggestions(input) {
const dynamicEndpoints = this.state.classNames.flatMap(className => [
`classes/${className}`,
`schemas/${className}`,
`aggregate/${className}`,
`purge/${className}`,
]);

const allEndpoints = [...PARSE_API_ENDPOINTS, ...dynamicEndpoints];

if (!input) {
return allEndpoints;
}

return allEndpoints.filter(
endpoint => endpoint.toLowerCase().indexOf(input.toLowerCase()) > -1
);
}

fetchUser() {
if (this.state.runAsIdentifier.length === 0) {
this.setState({ error: false, sessionToken: null });
Expand Down Expand Up @@ -210,11 +265,38 @@ export default class RestConsole extends Component {
/>
}
input={
<TextInput
value={this.state.endpoint}
monospace={true}
<Autocomplete
inputStyle={{
width: '100%',
height: '80px',
textAlign: 'center',
border: 'none',
fontSize: '16px',
fontFamily: '"Source Code Pro", "Courier New", monospace',
background: 'transparent',
padding: '0 6px',
outline: 'none',
}}
containerStyle={{ width: '100%', height: 'auto' }}
suggestionsStyle={{
maxHeight: '200px',
overflowY: 'auto',
fontFamily: '"Source Code Pro", "Courier New", monospace',
fontSize: '14px',
borderRadius: '0 0 5px 5px',
}}
suggestionsItemStyle={{
padding: '8px 12px',
}}
placeholder={'classes/_User'}
onChange={endpoint => this.setState({ endpoint })}
onSubmit={(endpoint) => {
if (!hasError) {
this.setState({ endpoint }, () => this.makeRequest());
}
}}
buildSuggestions={input => this.buildEndpointSuggestions(input)}
buildLabel={() => ''}
/>
}
/>
Expand Down
45 changes: 41 additions & 4 deletions src/dashboard/Data/Logs/Logs.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
import Button from 'components/Button/Button.react';
import CategoryList from 'components/CategoryList/CategoryList.react';
import DashboardView from 'dashboard/DashboardView.react';
import EmptyState from 'components/EmptyState/EmptyState.react';
Expand Down Expand Up @@ -32,6 +33,8 @@ class Logs extends DashboardView {
this.state = {
logs: undefined,
release: undefined,
loading: false,
hasMore: false,
};
}

Expand All @@ -47,14 +50,38 @@ class Logs extends DashboardView {
}
}

fetchLogs(app, type) {
fetchLogs(app, type, until) {
const PAGE_SIZE = 100;
const typeParam = (type || 'INFO').toUpperCase();
app.getLogs(typeParam).then(
logs => this.setState({ logs }),
() => this.setState({ logs: [] })
const options = { size: PAGE_SIZE };
if (until) {
options.until = until;
}
this.setState({ loading: true });
app.getLogs(typeParam, options).then(
newLogs => {
this.setState(prevState => ({
logs: until && Array.isArray(prevState.logs)
? prevState.logs.concat(newLogs)
: newLogs,
hasMore: newLogs.length >= PAGE_SIZE,
loading: false,
}));
},
() => this.setState({ logs: [], hasMore: false, loading: false })
);
}

handleLoadMore() {
const logs = this.state.logs;
if (!logs || logs.length === 0) {
return;
}
const oldestLog = logs[logs.length - 1];
const oldestTimestamp = oldestLog.timestamp.iso || oldestLog.timestamp;
this.fetchLogs(this.context, this.props.params.type, oldestTimestamp);
}

// As parse-server doesn't support (yet?) versioning, we are disabling
// this call in the meantime.

Expand Down Expand Up @@ -115,6 +142,16 @@ class Logs extends DashboardView {
<LogViewEntry key={timestamp} text={message} timestamp={timestamp} />
))}
</LogView>
{this.state.hasMore && (
<div className={styles.showMore}>
<Button
progress={this.state.loading}
color="blue"
value="Load more logs"
onClick={() => this.handleLoadMore()}
/>
</div>
)}
</div>
);
}
Expand Down
5 changes: 5 additions & 0 deletions src/dashboard/Data/Logs/Logs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@
right: 0;
bottom: 0;
}

.showMore {
padding: 20px;
text-align: center;
}
25 changes: 17 additions & 8 deletions src/lib/ParseApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,24 @@ export default class ParseApp {

/**
* Fetches scriptlogs from api.parse.com
* lines - maximum number of lines to fetch
* since - only fetch lines since this Date
* level - log level (info or error)
* options.from - only fetch logs after this date
* options.until - only fetch logs before this date
* options.size - maximum number of logs to fetch (default 100)
* options.order - sort order (asc or desc)
*/
getLogs(level, since) {
const path =
'scriptlog?level=' +
encodeURIComponent(level.toLowerCase()) +
'&n=100' +
(since ? '&startDate=' + encodeURIComponent(since.getTime()) : '');
getLogs(level, { from, until, size = 100, order } = {}) {
let path = 'scriptlog?level=' + encodeURIComponent(level.toLowerCase());
path += '&size=' + encodeURIComponent(size);
if (from) {
path += '&from=' + encodeURIComponent(from);
}
if (until) {
path += '&until=' + encodeURIComponent(until);
}
if (order) {
path += '&order=' + encodeURIComponent(order);
}
return this.apiRequest('GET', path, {}, { useMasterKey: true });
}

Expand Down
Loading