<commit_before>import * as React from "react"
import * as ReactDOM from "react-dom"
import * as lodash from "lodash"
import { observable, computed, action } from "mobx"
import urljoin from "url-join"

import { AdminApp } from "./AdminApp"
import { Json } from "charts/utils/Util"
import { queryParamsToStr } from "utils/client/url"

type HTTPMethod = "GET" | "PUT" | "POST" | "DELETE"

interface ClientSettings {
    ENV: "development" | "production"
    GITHUB_USERNAME: string
    EXPLORER: boolean
}

// Entry point for the grapher admin
// Currently just the editor, but eventually should expand to cover everything
export class Admin {
    @observable errorMessage?: {
        title: string
        content: string
        isFatal?: boolean
    }
    basePath: string
    username: string
    isSuperuser: boolean
    settings: ClientSettings

    constructor(props: {
        username: string
        isSuperuser: boolean
        settings: ClientSettings
    }) {
        this.basePath = "/admin"
        this.username = props.username
        this.isSuperuser = props.isSuperuser
        this.settings = props.settings
    }

    @observable currentRequests: Promise<Response>[] = []

    @computed get showLoadingIndicator() {
        return this.loadingIndicatorSetting === "default"
            ? this.currentRequests.length > 0
            : this.loadingIndicatorSetting === "loading"
    }

    @observable loadingIndicatorSetting: "loading" | "off" | "default" =
        "default"

    start(containerNode: HTMLElement) {
        ReactDOM.render(<AdminApp admin={this} />, containerNode)
    }

    url(path: string): string {
        return urljoin(this.basePath, path)
    }

    goto(path: string) {
        this.url(path)
    }

    // Make a request with no error or response handling
    async rawRequest(path: string, data: string | File, method: HTTPMethod) {
        const headers: any = {}
        const isFile = data instanceof File
        if (!isFile) {
            headers["Content-Type"] = "application/json"
        }
        headers["Accept"] = "application/json"

        return fetch(this.url(path), {
            method: method,
            credentials: "same-origin",
            headers: headers,
            body: method !== "GET" ? data : undefined
        })
    }

    // Make a request and expect JSON
    // If we can't retrieve and parse JSON, it is treated as a fatal/unexpected error
    async requestJSON(
        path: string,
        data: Json | File,
        method: HTTPMethod,
        opts: { onFailure?: "show" | "continue" } = {}
    ) {
        const onFailure = opts.onFailure || "show"

        let targetPath = path
        // Tack params on the end if it's a GET request
        if (method === "GET" && !lodash.isEmpty(data)) {
            targetPath += queryParamsToStr(data as Json)
        }

        let response: Response | undefined
        let text: string | undefined
        let json: Json

        let request: Promise<Response> | undefined
        try {
            request = this.rawRequest(
                targetPath,
                data instanceof File ? data : JSON.stringify(data),
                method
            )
            this.addRequest(request)

            response = await request
            text = await response.text()

            json = JSON.parse(text)
            if (json.error) {
                if (onFailure === "show")
                    this.setErrorMessage({
                        title: `Failed to ${method} ${targetPath} (${response.status})`,
                        content: json.error.message,
                        isFatal: response.status !== 404
                    })

                throw json.error
            }
        } catch (err) {
            if (onFailure === "show")
                this.setErrorMessage({
                    title:
                        `Failed to ${method} ${targetPath}` +
                        (response ? ` (${response.status})` : ""),
                    content: text || err,
                    isFatal: true
                })
            throw err
        } finally {
            if (request) this.removeRequest(request)
        }

        return json<commit_msg>fix (admin): show the human-readable error message<commit_after>import * as React from "react"
import * as ReactDOM from "react-dom"
import * as lodash from "lodash"
import { observable, computed, action } from "mobx"
import urljoin from "url-join"

import { AdminApp } from "./AdminApp"
import { Json } from "charts/utils/Util"
import { queryParamsToStr } from "utils/client/url"

type HTTPMethod = "GET" | "PUT" | "POST" | "DELETE"

interface ClientSettings {
    ENV: "development" | "production"
    GITHUB_USERNAME: string
    EXPLORER: boolean
}

// Entry point for the grapher admin
// Currently just the editor, but eventually should expand to cover everything
export class Admin {
    @observable errorMessage?: {
        title: string
        content: string
        isFatal?: boolean
    }
    basePath: string
    username: string
    isSuperuser: boolean
    settings: ClientSettings

    constructor(props: {
        username: string
        isSuperuser: boolean
        settings: ClientSettings
    }) {
        this.basePath = "/admin"
        this.username = props.username
        this.isSuperuser = props.isSuperuser
        this.settings = props.settings
    }

    @observable currentRequests: Promise<Response>[] = []

    @computed get showLoadingIndicator() {
        return this.loadingIndicatorSetting === "default"
            ? this.currentRequests.length > 0
            : this.loadingIndicatorSetting === "loading"
    }

    @observable loadingIndicatorSetting: "loading" | "off" | "default" =
        "default"

    start(containerNode: HTMLElement) {
        ReactDOM.render(<AdminApp admin={this} />, containerNode)
    }

    url(path: string): string {
        return urljoin(this.basePath, path)
    }

    goto(path: string) {
        this.url(path)
    }

    // Make a request with no error or response handling
    async rawRequest(path: string, data: string | File, method: HTTPMethod) {
        const headers: any = {}
        const isFile = data instanceof File
        if (!isFile) {
            headers["Content-Type"] = "application/json"
        }
        headers["Accept"] = "application/json"

        return fetch(this.url(path), {
            method: method,
            credentials: "same-origin",
            headers: headers,
            body: method !== "GET" ? data : undefined
        })
    }

    // Make a request and expect JSON
    // If we can't retrieve and parse JSON, it is treated as a fatal/unexpected error
    async requestJSON(
        path: string,
        data: Json | File,
        method: HTTPMethod,
        opts: { onFailure?: "show" | "continue" } = {}
    ) {
        const onFailure = opts.onFailure || "show"

        let targetPath = path
        // Tack params on the end if it's a GET request
        if (method === "GET" && !lodash.isEmpty(data)) {
            targetPath += queryParamsToStr(data as Json)
        }

        let response: Response | undefined
        let text: string | undefined
        let json: Json

        let request: Promise<Response> | undefined
        try {
            request = this.rawRequest(
                targetPath,
                data instanceof File ? data : JSON.stringify(data),
                method
            )
            this.addRequest(request)

            response = await request
            text = await response.text()

            json = JSON.parse(text)
            if (json.error) throw json.error
        } catch (err) {
            if (onFailure === "show")
                this.setErrorMessage({
                    title:
                        `Failed to ${method} ${targetPath}` +
                        (response ? ` (${response.status})` : ""),
                    content: err?.message || text || err,
                    isFatal: response?.status !== 404
                })
            throw err
        } finally {
            if (request) this.removeRequest(request)
        }

        return json
{'fraction_non_alphanumeric': 0.10321633851045615, 'fraction_numerical': 0.0009783539195303901, 'mean_word_length': 1.8755274261603376, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6687580', 'n_tokens_mistral': 2177, 'n_tokens_neox': 2104, 'n_words': 626}