Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
75 changes: 71 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,7 +33,10 @@ class Logs extends DashboardView {
this.state = {
logs: undefined,
release: undefined,
loading: false,
hasMore: false,
};
this.latestLogsRequestId = 0;
}

componentDidMount() {
Expand All @@ -47,14 +51,67 @@ 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;
}
const requestId = ++this.latestLogsRequestId;
this.setState({ loading: true });
app.getLogs(typeParam, options).then(
newLogs => {
if (requestId !== this.latestLogsRequestId) {
return;
}
this.setState(prevState => {
let merged;
if (until && Array.isArray(prevState.logs)) {
const existingKeys = new Set(
prevState.logs.map(l => {
const ts = l.timestamp.iso || l.timestamp;
return `${ts}|${l.message}`;
})
);
const unique = newLogs.filter(l => {
const ts = l.timestamp.iso || l.timestamp;
return !existingKeys.has(`${ts}|${l.message}`);
});
merged = prevState.logs.concat(unique);
} else {
merged = newLogs;
}
return {
logs: merged,
hasMore: newLogs.length > 0,
loading: false,
};
});
},
() => {
if (requestId !== this.latestLogsRequestId) {
return;
}
this.setState(prevState => ({
logs: prevState.logs || [],
hasMore: false,
loading: false,
}));
}
);
}

handleLoadMore() {
const logs = this.state.logs;
if (!logs || logs.length === 0 || this.state.loading) {
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 +172,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