mirror of
https://github.com/getredash/redash.git
synced 2025-12-25 01:03:20 -05:00
Compare commits
1 Commits
25.10.0-de
...
v25.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67a95e9dcc |
@@ -95,7 +95,7 @@ EOF
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV POETRY_VERSION=2.1.4
|
||||
ENV POETRY_VERSION=1.8.3
|
||||
ENV POETRY_HOME=/etc/poetry
|
||||
ENV POETRY_VIRTUALENVS_CREATE=false
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
@@ -46,7 +46,7 @@ server() {
|
||||
MAX_REQUESTS=${MAX_REQUESTS:-1000}
|
||||
MAX_REQUESTS_JITTER=${MAX_REQUESTS_JITTER:-100}
|
||||
TIMEOUT=${REDASH_GUNICORN_TIMEOUT:-60}
|
||||
exec /usr/local/bin/gunicorn -b 0.0.0.0:5000 --name redash -w${REDASH_WEB_WORKERS:-4} redash.wsgi:app --max-requests $MAX_REQUESTS --max-requests-jitter $MAX_REQUESTS_JITTER --timeout $TIMEOUT --limit-request-line ${REDASH_GUNICORN_LIMIT_REQUEST_LINE:-0}
|
||||
exec /usr/local/bin/gunicorn -b 0.0.0.0:5000 --name redash -w${REDASH_WEB_WORKERS:-4} redash.wsgi:app --max-requests $MAX_REQUESTS --max-requests-jitter $MAX_REQUESTS_JITTER --timeout $TIMEOUT
|
||||
}
|
||||
|
||||
create_db() {
|
||||
|
||||
@@ -15,7 +15,7 @@ body {
|
||||
display: table;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
height: calc(100% - 116px);
|
||||
height: calc(100vh - 116px);
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
|
||||
@@ -20,7 +20,7 @@ html {
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -35,7 +35,7 @@ body {
|
||||
}
|
||||
|
||||
#application-root {
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#application-root,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,4 +135,4 @@
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ body.fixed-layout {
|
||||
padding-bottom: 0;
|
||||
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
|
||||
.application-layout-content > div {
|
||||
display: flex;
|
||||
@@ -90,7 +90,7 @@ body.fixed-layout {
|
||||
.embed__vis {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: calc(~'100% - 25px');
|
||||
height: calc(~'100vh - 25px');
|
||||
|
||||
> .embed-heading {
|
||||
flex: 0 0 auto;
|
||||
|
||||
@@ -7,10 +7,10 @@ body #application-root {
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
padding-bottom: 0 !important;
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
|
||||
.application-layout-side-menu {
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
|
||||
@media @mobileBreakpoint {
|
||||
@@ -47,10 +47,6 @@ body #application-root {
|
||||
}
|
||||
}
|
||||
|
||||
body > section {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body.fixed-layout #application-root {
|
||||
.application-layout-content {
|
||||
padding-bottom: 0;
|
||||
|
||||
@@ -10,10 +10,6 @@ export interface PaginationOptions {
|
||||
itemsPerPage?: number;
|
||||
}
|
||||
|
||||
export interface SearchOptions {
|
||||
isServerSideFTS?: boolean;
|
||||
}
|
||||
|
||||
export interface Controller<I, P = any> {
|
||||
params: P; // TODO: Find out what params is (except merging with props)
|
||||
|
||||
@@ -22,7 +18,7 @@ export interface Controller<I, P = any> {
|
||||
|
||||
// search
|
||||
searchTerm?: string;
|
||||
updateSearch: (searchTerm: string, searchOptions?: SearchOptions) => void;
|
||||
updateSearch: (searchTerm: string) => void;
|
||||
|
||||
// tags
|
||||
selectedTags: string[];
|
||||
@@ -98,7 +94,7 @@ export interface ItemsListWrappedComponentProps<I, P = any> {
|
||||
export function wrap<I, P = any>(
|
||||
WrappedComponent: React.ComponentType<ItemsListWrappedComponentProps<I>>,
|
||||
createItemsSource: () => ItemsSource,
|
||||
createStateStorage: ( { ...props }) => StateStorage
|
||||
createStateStorage: () => StateStorage
|
||||
) {
|
||||
class ItemsListWrapper extends React.Component<ItemsListWrapperProps, ItemsListWrapperState<I, P>> {
|
||||
private _itemsSource: ItemsSource;
|
||||
@@ -121,7 +117,7 @@ export function wrap<I, P = any>(
|
||||
constructor(props: ItemsListWrapperProps) {
|
||||
super(props);
|
||||
|
||||
const stateStorage = createStateStorage({ ...props });
|
||||
const stateStorage = createStateStorage();
|
||||
const itemsSource = createItemsSource();
|
||||
this._itemsSource = itemsSource;
|
||||
|
||||
|
||||
@@ -135,13 +135,13 @@ export class ItemsSource {
|
||||
this._changed({ sorting: true });
|
||||
};
|
||||
|
||||
updateSearch = (searchTerm, options) => {
|
||||
updateSearch = (searchTerm) => {
|
||||
// here we update state directly, but later `fetchData` will update it properly
|
||||
this._searchTerm = searchTerm;
|
||||
// in search mode ignore the ordering and use the ranking order
|
||||
// provided by the server-side FTS backend instead, unless it was
|
||||
// requested by the user by actively ordering in search mode
|
||||
if (searchTerm === "" || !options?.isServerSideFTS) {
|
||||
if (searchTerm === "") {
|
||||
this._sorter.setField(this._savedOrderByField); // restore ordering
|
||||
} else {
|
||||
this._sorter.setField(null);
|
||||
|
||||
@@ -47,30 +47,20 @@ function SchemaItem({ item, expanded, onToggle, onSelect, ...props }) {
|
||||
return (
|
||||
<div {...props}>
|
||||
<div className="schema-list-item">
|
||||
<Tooltip
|
||||
title={item.description}
|
||||
mouseEnterDelay={0}
|
||||
mouseLeaveDelay={0}
|
||||
placement="rightTop"
|
||||
trigger={item.description ? "hover" : ""}
|
||||
overlayStyle={{ whiteSpace: "pre-line" }}
|
||||
>
|
||||
<PlainButton className="table-name" onClick={onToggle}>
|
||||
<i className="fa fa-table m-r-5" aria-hidden="true" />
|
||||
<strong>
|
||||
<span title={item.name}>{tableDisplayName}</span>
|
||||
{!isNil(item.size) && <span> ({item.size})</span>}
|
||||
</strong>
|
||||
</PlainButton>
|
||||
</Tooltip>
|
||||
<PlainButton className="table-name" onClick={onToggle}>
|
||||
<i className="fa fa-table m-r-5" aria-hidden="true" />
|
||||
<strong>
|
||||
<span title={item.name}>{tableDisplayName}</span>
|
||||
{!isNil(item.size) && <span> ({item.size})</span>}
|
||||
</strong>
|
||||
</PlainButton>
|
||||
<Tooltip
|
||||
title="Insert table name into query text"
|
||||
mouseEnterDelay={0}
|
||||
mouseLeaveDelay={0}
|
||||
placement="topRight"
|
||||
arrowPointAtCenter
|
||||
>
|
||||
<PlainButton className="copy-to-editor" onClick={(e) => handleSelect(e, item.name)}>
|
||||
arrowPointAtCenter>
|
||||
<PlainButton className="copy-to-editor" onClick={e => handleSelect(e, item.name)}>
|
||||
<i className="fa fa-angle-double-right" aria-hidden="true" />
|
||||
</PlainButton>
|
||||
</Tooltip>
|
||||
@@ -80,22 +70,16 @@ function SchemaItem({ item, expanded, onToggle, onSelect, ...props }) {
|
||||
{item.loading ? (
|
||||
<div className="table-open">Loading...</div>
|
||||
) : (
|
||||
map(item.columns, (column) => {
|
||||
map(item.columns, column => {
|
||||
const columnName = get(column, "name");
|
||||
const columnType = get(column, "type");
|
||||
const columnDescription = get(column, "description");
|
||||
return (
|
||||
<Tooltip
|
||||
title={"Insert column name into query text" + (columnDescription ? "\n" + columnDescription : "")}
|
||||
title="Insert column name into query text"
|
||||
mouseEnterDelay={0}
|
||||
mouseLeaveDelay={0}
|
||||
placement="rightTop"
|
||||
>
|
||||
<PlainButton
|
||||
key={columnName}
|
||||
className="table-open-item"
|
||||
onClick={(e) => handleSelect(e, columnName)}
|
||||
>
|
||||
placement="rightTop">
|
||||
<PlainButton key={columnName} className="table-open-item" onClick={e => handleSelect(e, columnName)}>
|
||||
<div>
|
||||
{columnName} {columnType && <span className="column-type">{columnType}</span>}
|
||||
</div>
|
||||
@@ -184,7 +168,7 @@ export function SchemaList({ loading, schema, expandedFlags, onTableExpand, onIt
|
||||
}
|
||||
|
||||
export function applyFilterOnSchema(schema, filterString) {
|
||||
const filters = filter(filterString.toLowerCase().split(/\s+/), (s) => s.length > 0);
|
||||
const filters = filter(filterString.toLowerCase().split(/\s+/), s => s.length > 0);
|
||||
|
||||
// Empty string: return original schema
|
||||
if (filters.length === 0) {
|
||||
@@ -197,9 +181,9 @@ export function applyFilterOnSchema(schema, filterString) {
|
||||
const columnFilter = filters[0];
|
||||
return filter(
|
||||
schema,
|
||||
(item) =>
|
||||
item =>
|
||||
includes(item.name.toLowerCase(), nameFilter) ||
|
||||
some(item.columns, (column) => includes(get(column, "name").toLowerCase(), columnFilter))
|
||||
some(item.columns, column => includes(get(column, "name").toLowerCase(), columnFilter))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -207,11 +191,11 @@ export function applyFilterOnSchema(schema, filterString) {
|
||||
const nameFilter = filters[0];
|
||||
const columnFilter = filters[1];
|
||||
return filter(
|
||||
map(schema, (item) => {
|
||||
map(schema, item => {
|
||||
if (includes(item.name.toLowerCase(), nameFilter)) {
|
||||
item = {
|
||||
...item,
|
||||
columns: filter(item.columns, (column) => includes(get(column, "name").toLowerCase(), columnFilter)),
|
||||
columns: filter(item.columns, column => includes(get(column, "name").toLowerCase(), columnFilter)),
|
||||
};
|
||||
return item.columns.length > 0 ? item : null;
|
||||
}
|
||||
@@ -259,7 +243,7 @@ export default function SchemaBrowser({
|
||||
placeholder="Search schema..."
|
||||
aria-label="Search schema"
|
||||
disabled={schema.length === 0}
|
||||
onChange={(event) => handleFilterChange(event.target.value)}
|
||||
onChange={event => handleFilterChange(event.target.value)}
|
||||
/>
|
||||
|
||||
<Tooltip title="Refresh Schema">
|
||||
|
||||
@@ -81,19 +81,12 @@ function DashboardListExtraActions(props) {
|
||||
}
|
||||
|
||||
function DashboardList({ controller }) {
|
||||
let usedListColumns = listColumns;
|
||||
if (controller.params.currentPage === "favorites") {
|
||||
usedListColumns = [
|
||||
...usedListColumns,
|
||||
Columns.dateTime.sortable({ title: "Starred At", field: "starred_at", width: "1%" }),
|
||||
];
|
||||
}
|
||||
const {
|
||||
areExtraActionsAvailable,
|
||||
listColumns: tableColumns,
|
||||
Component: ExtraActionsComponent,
|
||||
selectedItems,
|
||||
} = useItemsListExtraActions(controller, usedListColumns, DashboardListExtraActions);
|
||||
} = useItemsListExtraActions(controller, listColumns, DashboardListExtraActions);
|
||||
|
||||
return (
|
||||
<div className="page-dashboard-list">
|
||||
@@ -146,9 +139,9 @@ function DashboardList({ controller }) {
|
||||
showPageSizeSelect
|
||||
totalCount={controller.totalItemsCount}
|
||||
pageSize={controller.itemsPerPage}
|
||||
onPageSizeChange={(itemsPerPage) => controller.updatePagination({ itemsPerPage })}
|
||||
onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}
|
||||
page={controller.page}
|
||||
onChange={(page) => controller.updatePagination({ page })}
|
||||
onChange={page => controller.updatePagination({ page })}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
@@ -177,10 +170,10 @@ const DashboardListPage = itemsList(
|
||||
}[currentPage];
|
||||
},
|
||||
getItemProcessor() {
|
||||
return (item) => new Dashboard(item);
|
||||
return item => new Dashboard(item);
|
||||
},
|
||||
}),
|
||||
({ ...props }) => new UrlStateStorage({ orderByField: props.orderByField ?? "created_at", orderByReverse: true })
|
||||
() => new UrlStateStorage({ orderByField: "created_at", orderByReverse: true })
|
||||
);
|
||||
|
||||
routes.register(
|
||||
@@ -188,7 +181,7 @@ routes.register(
|
||||
routeWithUserSession({
|
||||
path: "/dashboards",
|
||||
title: "Dashboards",
|
||||
render: (pageProps) => <DashboardListPage {...pageProps} currentPage="all" />,
|
||||
render: pageProps => <DashboardListPage {...pageProps} currentPage="all" />,
|
||||
})
|
||||
);
|
||||
routes.register(
|
||||
@@ -196,7 +189,7 @@ routes.register(
|
||||
routeWithUserSession({
|
||||
path: "/dashboards/favorites",
|
||||
title: "Favorite Dashboards",
|
||||
render: (pageProps) => <DashboardListPage {...pageProps} currentPage="favorites" orderByField="starred_at" />,
|
||||
render: pageProps => <DashboardListPage {...pageProps} currentPage="favorites" />,
|
||||
})
|
||||
);
|
||||
routes.register(
|
||||
@@ -204,6 +197,6 @@ routes.register(
|
||||
routeWithUserSession({
|
||||
path: "/dashboards/my",
|
||||
title: "My Dashboards",
|
||||
render: (pageProps) => <DashboardListPage {...pageProps} currentPage="my" />,
|
||||
render: pageProps => <DashboardListPage {...pageProps} currentPage="my" />,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
|
||||
> .container {
|
||||
min-height: calc(100% - 95px);
|
||||
min-height: calc(100vh - 95px);
|
||||
}
|
||||
|
||||
.loading-message {
|
||||
|
||||
@@ -15,7 +15,7 @@ export function FavoriteList({ title, resource, itemUrl, emptyState }) {
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
resource
|
||||
.favorites({ order: "-starred_at" })
|
||||
.favorites()
|
||||
.then(({ results }) => setItems(results))
|
||||
.finally(() => setLoading(false));
|
||||
}, [resource]);
|
||||
@@ -28,7 +28,7 @@ export function FavoriteList({ title, resource, itemUrl, emptyState }) {
|
||||
</div>
|
||||
{!isEmpty(items) && (
|
||||
<div role="list" className="list-group">
|
||||
{items.map((item) => (
|
||||
{items.map(item => (
|
||||
<Link key={itemUrl(item)} role="listitem" className="list-group-item" href={itemUrl(item)}>
|
||||
<span className="btn-favorite m-r-5">
|
||||
<i className="fa fa-star" aria-hidden="true" />
|
||||
@@ -61,7 +61,7 @@ export function DashboardAndQueryFavoritesList() {
|
||||
<FavoriteList
|
||||
title="Favorite Dashboards"
|
||||
resource={Dashboard}
|
||||
itemUrl={(dashboard) => dashboard.url}
|
||||
itemUrl={dashboard => dashboard.url}
|
||||
emptyState={
|
||||
<p>
|
||||
<span className="btn-favorite m-r-5">
|
||||
@@ -76,7 +76,7 @@ export function DashboardAndQueryFavoritesList() {
|
||||
<FavoriteList
|
||||
title="Favorite Queries"
|
||||
resource={Query}
|
||||
itemUrl={(query) => `queries/${query.id}`}
|
||||
itemUrl={query => `queries/${query.id}`}
|
||||
emptyState={
|
||||
<p>
|
||||
<span className="btn-favorite m-r-5">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import cx from "classnames";
|
||||
|
||||
import routeWithUserSession from "@/components/ApplicationArea/routeWithUserSession";
|
||||
@@ -20,7 +20,7 @@ import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTab
|
||||
import Layout from "@/components/layouts/ContentWithSidebar";
|
||||
|
||||
import { Query } from "@/services/query";
|
||||
import { clientConfig, currentUser } from "@/services/auth";
|
||||
import { currentUser } from "@/services/auth";
|
||||
import location from "@/services/location";
|
||||
import routes from "@/services/routes";
|
||||
|
||||
@@ -95,39 +95,25 @@ function QueriesList({ controller }) {
|
||||
const controllerRef = useRef();
|
||||
controllerRef.current = controller;
|
||||
|
||||
const updateSearch = useCallback(
|
||||
(searchTemm) => {
|
||||
controller.updateSearch(searchTemm, { isServerSideFTS: !clientConfig.multiByteSearchEnabled });
|
||||
},
|
||||
[controller]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const unlistenLocationChanges = location.listen((unused, action) => {
|
||||
const searchTerm = location.search.q || "";
|
||||
if (action === "PUSH" && searchTerm !== controllerRef.current.searchTerm) {
|
||||
updateSearch(searchTerm);
|
||||
controllerRef.current.updateSearch(searchTerm);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlistenLocationChanges();
|
||||
};
|
||||
}, [updateSearch]);
|
||||
}, []);
|
||||
|
||||
let usedListColumns = listColumns;
|
||||
if (controller.params.currentPage === "favorites") {
|
||||
usedListColumns = [
|
||||
...usedListColumns,
|
||||
Columns.dateTime.sortable({ title: "Starred At", field: "starred_at", width: "1%" }),
|
||||
];
|
||||
}
|
||||
const {
|
||||
areExtraActionsAvailable,
|
||||
listColumns: tableColumns,
|
||||
Component: ExtraActionsComponent,
|
||||
selectedItems,
|
||||
} = useItemsListExtraActions(controller, usedListColumns, QueriesListExtraActions);
|
||||
} = useItemsListExtraActions(controller, listColumns, QueriesListExtraActions);
|
||||
|
||||
return (
|
||||
<div className="page-queries-list">
|
||||
@@ -149,7 +135,7 @@ function QueriesList({ controller }) {
|
||||
placeholder="Search Queries..."
|
||||
label="Search queries"
|
||||
value={controller.searchTerm}
|
||||
onChange={updateSearch}
|
||||
onChange={controller.updateSearch}
|
||||
/>
|
||||
<Sidebar.Menu items={sidebarMenu} selected={controller.params.currentPage} />
|
||||
<Sidebar.Tags url="api/queries/tags" onChange={controller.updateSelectedTags} showUnselectAll />
|
||||
@@ -214,7 +200,7 @@ const QueriesListPage = itemsList(
|
||||
return (item) => new Query(item);
|
||||
},
|
||||
}),
|
||||
({ ...props }) => new UrlStateStorage({ orderByField: props.orderByField ?? "created_at", orderByReverse: true })
|
||||
() => new UrlStateStorage({ orderByField: "created_at", orderByReverse: true })
|
||||
);
|
||||
|
||||
routes.register(
|
||||
@@ -230,7 +216,7 @@ routes.register(
|
||||
routeWithUserSession({
|
||||
path: "/queries/favorites",
|
||||
title: "Favorite Queries",
|
||||
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="favorites" orderByField="starred_at" />,
|
||||
render: (pageProps) => <QueriesListPage {...pageProps} currentPage="favorites" />,
|
||||
})
|
||||
);
|
||||
routes.register(
|
||||
|
||||
@@ -9,7 +9,7 @@ function normalizeLocation(rawLocation) {
|
||||
const result = {};
|
||||
|
||||
result.path = pathname;
|
||||
result.search = mapValues(qs.parse(search), (value) => (isNil(value) ? true : value));
|
||||
result.search = mapValues(qs.parse(search), value => (isNil(value) ? true : value));
|
||||
result.hash = trimStart(hash, "#");
|
||||
result.url = `${pathname}${search}${hash}`;
|
||||
|
||||
@@ -27,7 +27,7 @@ const location = {
|
||||
|
||||
confirmChange(handler) {
|
||||
if (isFunction(handler)) {
|
||||
return history.block((nextLocation) => {
|
||||
return history.block(nextLocation => {
|
||||
return handler(normalizeLocation(nextLocation), location);
|
||||
});
|
||||
} else {
|
||||
@@ -60,18 +60,12 @@ const location = {
|
||||
// serialize search and keep existing search parameters (!)
|
||||
if (isObject(newLocation.search)) {
|
||||
newLocation.search = omitBy(extend({}, location.search, newLocation.search), isNil);
|
||||
newLocation.search = mapValues(newLocation.search, (value) => (value === true ? null : value));
|
||||
newLocation.search = mapValues(newLocation.search, value => (value === true ? null : value));
|
||||
newLocation.search = qs.stringify(newLocation.search);
|
||||
}
|
||||
}
|
||||
if (replace) {
|
||||
if (
|
||||
newLocation.pathname !== location.path ||
|
||||
newLocation.search !== qs.stringify(location.search) ||
|
||||
newLocation.hash !== location.hash
|
||||
) {
|
||||
history.replace(newLocation);
|
||||
}
|
||||
history.replace(newLocation);
|
||||
} else {
|
||||
history.push(newLocation);
|
||||
}
|
||||
|
||||
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "redash-client",
|
||||
"version": "25.10.0-dev",
|
||||
"version": "25.8.0",
|
||||
"description": "The frontend part of Redash.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -47,7 +47,7 @@
|
||||
"@ant-design/icons": "^4.2.1",
|
||||
"@redash/viz": "file:viz-lib",
|
||||
"ace-builds": "^1.4.12",
|
||||
"antd": "4.4.3",
|
||||
"antd": "^4.4.3",
|
||||
"axios": "0.27.2",
|
||||
"axios-auth-refresh": "3.3.6",
|
||||
"bootstrap": "^3.4.1",
|
||||
@@ -100,7 +100,6 @@
|
||||
"@types/sql-formatter": "^2.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"assert": "^2.1.0",
|
||||
"atob": "^2.1.2",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-jest": "^24.1.0",
|
||||
@@ -140,23 +139,20 @@
|
||||
"mockdate": "^2.0.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "3.3.2",
|
||||
"process": "^0.11.10",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react-refresh": "^0.14.0",
|
||||
"react-test-renderer": "^16.14.0",
|
||||
"request-cookies": "^1.1.0",
|
||||
"source-map-loader": "^1.1.3",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"typescript": "4.1.2",
|
||||
"url": "^0.11.4",
|
||||
"typescript": "^4.1.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.101.3",
|
||||
"webpack-build-notifier": "^3.0.1",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-build-notifier": "^2.3.0",
|
||||
"webpack-bundle-analyzer": "^4.9.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-manifest-plugin": "^5.0.1"
|
||||
"webpack-manifest-plugin": "^2.0.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "^2.3.2"
|
||||
|
||||
328
poetry.lock
generated
328
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,5 @@
|
||||
[project]
|
||||
name = "redash"
|
||||
version = "25.10.0-dev"
|
||||
requires-python = ">=3.8"
|
||||
description = "Make Your Company Data Driven. Connect to any data source, easily visualize, dashboard and share your data."
|
||||
authors = [
|
||||
{ name = "Arik Fraimovich", email = "<arik@redash.io>" }
|
||||
]
|
||||
# to be added to/removed from the mailing list, please reach out to Arik via the above email or Discord
|
||||
maintainers = [
|
||||
{ name = "Redash maintainers and contributors", email = "<maintainers@redash.io>" }
|
||||
]
|
||||
readme = "README.md"
|
||||
dependencies = []
|
||||
|
||||
[tool.black]
|
||||
target-version = ['py38']
|
||||
@@ -22,6 +10,17 @@ force-exclude = '''
|
||||
)/
|
||||
'''
|
||||
|
||||
[tool.poetry]
|
||||
name = "redash"
|
||||
version = "25.8.0"
|
||||
description = "Make Your Company Data Driven. Connect to any data source, easily visualize, dashboard and share your data."
|
||||
authors = ["Arik Fraimovich <arik@redash.io>"]
|
||||
# to be added to/removed from the mailing list, please reach out to Arik via the above email or Discord
|
||||
maintainers = [
|
||||
"Redash maintainers and contributors <maintainers@redash.io>",
|
||||
]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.8,<3.11"
|
||||
advocate = "1.0.0"
|
||||
|
||||
@@ -14,7 +14,7 @@ from redash.app import create_app # noqa
|
||||
from redash.destinations import import_destinations
|
||||
from redash.query_runner import import_query_runners
|
||||
|
||||
__version__ = "25.10.0-dev"
|
||||
__version__ = "25.8.0"
|
||||
|
||||
|
||||
if os.environ.get("REMOTE_DEBUG"):
|
||||
|
||||
@@ -278,7 +278,6 @@ def client_config():
|
||||
"showPermissionsControl": current_org.get_setting("feature_show_permissions_control"),
|
||||
"hidePlotlyModeBar": current_org.get_setting("hide_plotly_mode_bar"),
|
||||
"disablePublicUrls": current_org.get_setting("disable_public_urls"),
|
||||
"multiByteSearchEnabled": current_org.get_setting("multi_byte_search_enabled"),
|
||||
"allowCustomJSVisualizations": settings.FEATURE_ALLOW_CUSTOM_JS_VISUALIZATIONS,
|
||||
"autoPublishNamedQueries": settings.FEATURE_AUTO_PUBLISH_NAMED_QUERIES,
|
||||
"extendedAlertOptions": settings.FEATURE_EXTENDED_ALERT_OPTIONS,
|
||||
|
||||
@@ -26,8 +26,6 @@ order_map = {
|
||||
"-name": "-lowercase_name",
|
||||
"created_at": "created_at",
|
||||
"-created_at": "-created_at",
|
||||
"starred_at": "favorites-created_at",
|
||||
"-starred_at": "-favorites-created_at",
|
||||
}
|
||||
|
||||
order_results = partial(_order_results, default_order="-created_at", allowed_orders=order_map)
|
||||
|
||||
@@ -44,8 +44,6 @@ order_map = {
|
||||
"-executed_at": "-query_results-retrieved_at",
|
||||
"created_by": "users-name",
|
||||
"-created_by": "-users-name",
|
||||
"starred_at": "favorites-created_at",
|
||||
"-starred_at": "-favorites-created_at",
|
||||
}
|
||||
|
||||
order_results = partial(_order_results, default_order="-created_at", allowed_orders=order_map)
|
||||
|
||||
@@ -228,7 +228,7 @@ class DataSource(BelongsToOrgMixin, db.Model):
|
||||
|
||||
def _sort_schema(self, schema):
|
||||
return [
|
||||
{**i, "columns": sorted(i["columns"], key=lambda x: x["name"] if isinstance(x, dict) else x)}
|
||||
{"name": i["name"], "columns": sorted(i["columns"], key=lambda x: x["name"] if isinstance(x, dict) else x)}
|
||||
for i in sorted(schema, key=lambda x: x["name"])
|
||||
]
|
||||
|
||||
@@ -1145,19 +1145,15 @@ class Dashboard(ChangeTrackingMixin, TimestampMixin, BelongsToOrgMixin, db.Model
|
||||
def favorites(cls, user, base_query=None):
|
||||
if base_query is None:
|
||||
base_query = cls.all(user.org, user.group_ids, user.id)
|
||||
return (
|
||||
base_query.distinct(cls.lowercase_name, Dashboard.created_at, Dashboard.slug, Favorite.created_at)
|
||||
.join(
|
||||
(
|
||||
Favorite,
|
||||
and_(
|
||||
Favorite.object_type == "Dashboard",
|
||||
Favorite.object_id == Dashboard.id,
|
||||
),
|
||||
)
|
||||
return base_query.join(
|
||||
(
|
||||
Favorite,
|
||||
and_(
|
||||
Favorite.object_type == "Dashboard",
|
||||
Favorite.object_id == Dashboard.id,
|
||||
),
|
||||
)
|
||||
.filter(Favorite.user_id == user.id)
|
||||
)
|
||||
).filter(Favorite.user_id == user.id)
|
||||
|
||||
@classmethod
|
||||
def by_user(cls, user):
|
||||
|
||||
@@ -156,11 +156,6 @@ class BigQuery(BaseSQLQueryRunner):
|
||||
"secret": ["jsonKeyFile"],
|
||||
}
|
||||
|
||||
def annotate_query(self, query, metadata):
|
||||
# Remove "Job ID" before annotating the query to avoid cache misses
|
||||
metadata = {k: v for k, v in metadata.items() if k != "Job ID"}
|
||||
return super().annotate_query(query, metadata)
|
||||
|
||||
def _get_bigquery_service(self):
|
||||
socket.setdefaulttimeout(settings.BIGQUERY_HTTP_TIMEOUT)
|
||||
|
||||
@@ -220,12 +215,11 @@ class BigQuery(BaseSQLQueryRunner):
|
||||
job_data = self._get_job_data(query)
|
||||
insert_response = jobs.insert(projectId=project_id, body=job_data).execute()
|
||||
self.current_job_id = insert_response["jobReference"]["jobId"]
|
||||
self.current_job_location = insert_response["jobReference"]["location"]
|
||||
current_row = 0
|
||||
query_reply = _get_query_results(
|
||||
jobs,
|
||||
project_id=project_id,
|
||||
location=self.current_job_location,
|
||||
location=self._get_location(),
|
||||
job_id=self.current_job_id,
|
||||
start_index=current_row,
|
||||
)
|
||||
@@ -242,11 +236,13 @@ class BigQuery(BaseSQLQueryRunner):
|
||||
|
||||
query_result_request = {
|
||||
"projectId": project_id,
|
||||
"jobId": self.current_job_id,
|
||||
"jobId": query_reply["jobReference"]["jobId"],
|
||||
"startIndex": current_row,
|
||||
"location": self.current_job_location,
|
||||
}
|
||||
|
||||
if self._get_location():
|
||||
query_result_request["location"] = self._get_location()
|
||||
|
||||
query_reply = jobs.getQueryResults(**query_result_request).execute()
|
||||
|
||||
columns = [
|
||||
@@ -308,70 +304,32 @@ class BigQuery(BaseSQLQueryRunner):
|
||||
datasets = self._get_project_datasets(project_id)
|
||||
|
||||
query_base = """
|
||||
SELECT table_schema, table_name, field_path, data_type, description
|
||||
SELECT table_schema, table_name, field_path, data_type
|
||||
FROM `{dataset_id}`.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS
|
||||
WHERE table_schema NOT IN ('information_schema')
|
||||
"""
|
||||
|
||||
table_query_base = """
|
||||
SELECT table_schema, table_name, JSON_VALUE(option_value) as table_description
|
||||
FROM `{dataset_id}`.INFORMATION_SCHEMA.TABLE_OPTIONS
|
||||
WHERE table_schema NOT IN ('information_schema')
|
||||
AND option_name = 'description'
|
||||
"""
|
||||
|
||||
location_dataset_ids = {}
|
||||
schema = {}
|
||||
queries = []
|
||||
for dataset in datasets:
|
||||
dataset_id = dataset["datasetReference"]["datasetId"]
|
||||
location = dataset["location"]
|
||||
if self._get_location() and location != self._get_location():
|
||||
logger.debug("dataset location is different: %s", location)
|
||||
continue
|
||||
query = query_base.format(dataset_id=dataset_id)
|
||||
queries.append(query)
|
||||
|
||||
if location not in location_dataset_ids:
|
||||
location_dataset_ids[location] = []
|
||||
location_dataset_ids[location].append(dataset_id)
|
||||
query = "\nUNION ALL\n".join(queries)
|
||||
results, error = self.run_query(query, None)
|
||||
if error is not None:
|
||||
self._handle_run_query_error(error)
|
||||
|
||||
for location, datasets in location_dataset_ids.items():
|
||||
queries = []
|
||||
for dataset_id in datasets:
|
||||
query = query_base.format(dataset_id=dataset_id)
|
||||
queries.append(query)
|
||||
|
||||
query = "\nUNION ALL\n".join(queries)
|
||||
results, error = self.run_query(query, None)
|
||||
if error is not None:
|
||||
self._handle_run_query_error(error)
|
||||
|
||||
for row in results["rows"]:
|
||||
table_name = "{0}.{1}".format(row["table_schema"], row["table_name"])
|
||||
if table_name not in schema:
|
||||
schema[table_name] = {"name": table_name, "columns": []}
|
||||
schema[table_name]["columns"].append(
|
||||
{
|
||||
"name": row["field_path"],
|
||||
"type": row["data_type"],
|
||||
"description": row["description"],
|
||||
}
|
||||
)
|
||||
|
||||
table_queries = []
|
||||
for dataset_id in datasets:
|
||||
table_query = table_query_base.format(dataset_id=dataset_id)
|
||||
table_queries.append(table_query)
|
||||
|
||||
table_query = "\nUNION ALL\n".join(table_queries)
|
||||
results, error = self.run_query(table_query, None)
|
||||
if error is not None:
|
||||
self._handle_run_query_error(error)
|
||||
|
||||
for row in results["rows"]:
|
||||
table_name = "{0}.{1}".format(row["table_schema"], row["table_name"])
|
||||
if table_name not in schema:
|
||||
schema[table_name] = {"name": table_name, "columns": []}
|
||||
if "table_description" in row:
|
||||
schema[table_name]["description"] = row["table_description"]
|
||||
for row in results["rows"]:
|
||||
table_name = "{0}.{1}".format(row["table_schema"], row["table_name"])
|
||||
if table_name not in schema:
|
||||
schema[table_name] = {"name": table_name, "columns": []}
|
||||
schema[table_name]["columns"].append({"name": row["field_path"], "type": row["data_type"]})
|
||||
|
||||
return list(schema.values())
|
||||
|
||||
@@ -405,7 +363,7 @@ class BigQuery(BaseSQLQueryRunner):
|
||||
self._get_bigquery_service().jobs().cancel(
|
||||
projectId=self._get_project_id(),
|
||||
jobId=self.current_job_id,
|
||||
location=self.current_job_location,
|
||||
location=self._get_location(),
|
||||
).execute()
|
||||
|
||||
raise
|
||||
|
||||
@@ -34,13 +34,9 @@ class ResultSet:
|
||||
|
||||
def parse_issue(issue, field_mapping): # noqa: C901
|
||||
result = OrderedDict()
|
||||
result["key"] = issue["key"]
|
||||
|
||||
# Handle API v3 response format: key field may be missing, use id as fallback
|
||||
result["key"] = issue.get("key", issue.get("id", "unknown"))
|
||||
|
||||
# Handle API v3 response format: fields may be missing
|
||||
fields = issue.get("fields", {})
|
||||
for k, v in fields.items(): #
|
||||
for k, v in issue["fields"].items(): #
|
||||
output_name = field_mapping.get_output_field_name(k)
|
||||
member_names = field_mapping.get_dict_members(k)
|
||||
|
||||
@@ -102,9 +98,7 @@ def parse_issues(data, field_mapping):
|
||||
|
||||
def parse_count(data):
|
||||
results = ResultSet()
|
||||
# API v3 may not return 'total' field, fallback to counting issues
|
||||
count = data.get("total", len(data.get("issues", [])))
|
||||
results.add_row({"count": count})
|
||||
results.add_row({"count": data["total"]})
|
||||
return results
|
||||
|
||||
|
||||
@@ -166,26 +160,18 @@ class JiraJQL(BaseHTTPQueryRunner):
|
||||
self.syntax = "json"
|
||||
|
||||
def run_query(self, query, user):
|
||||
# Updated to API v3 endpoint, fix double slash issue
|
||||
jql_url = "{}/rest/api/3/search/jql".format(self.configuration["url"].rstrip("/"))
|
||||
jql_url = "{}/rest/api/2/search".format(self.configuration["url"])
|
||||
|
||||
query = json_loads(query)
|
||||
query_type = query.pop("queryType", "select")
|
||||
field_mapping = FieldMapping(query.pop("fieldMapping", {}))
|
||||
|
||||
# API v3 requires mandatory jql parameter with restrictions
|
||||
if "jql" not in query or not query["jql"]:
|
||||
query["jql"] = "created >= -30d order by created DESC"
|
||||
|
||||
if query_type == "count":
|
||||
query["maxResults"] = 1
|
||||
query["fields"] = ""
|
||||
else:
|
||||
query["maxResults"] = query.get("maxResults", 1000)
|
||||
|
||||
if "fields" not in query:
|
||||
query["fields"] = "*all"
|
||||
|
||||
response, error = self.get_response(jql_url, params=query)
|
||||
if error is not None:
|
||||
return None, error
|
||||
@@ -196,15 +182,17 @@ class JiraJQL(BaseHTTPQueryRunner):
|
||||
results = parse_count(data)
|
||||
else:
|
||||
results = parse_issues(data, field_mapping)
|
||||
index = data["startAt"] + data["maxResults"]
|
||||
|
||||
# API v3 uses token-based pagination instead of startAt/total
|
||||
while not data.get("isLast", True) and "nextPageToken" in data:
|
||||
query["nextPageToken"] = data["nextPageToken"]
|
||||
while data["total"] > index:
|
||||
query["startAt"] = index
|
||||
response, error = self.get_response(jql_url, params=query)
|
||||
if error is not None:
|
||||
return None, error
|
||||
|
||||
data = response.json()
|
||||
index = data["startAt"] + data["maxResults"]
|
||||
|
||||
addl_results = parse_issues(data, field_mapping)
|
||||
results.merge(addl_results)
|
||||
|
||||
|
||||
@@ -150,9 +150,7 @@ class Mysql(BaseSQLQueryRunner):
|
||||
query = """
|
||||
SELECT col.table_schema as table_schema,
|
||||
col.table_name as table_name,
|
||||
col.column_name as column_name,
|
||||
col.data_type as data_type,
|
||||
col.column_comment as column_comment
|
||||
col.column_name as column_name
|
||||
FROM `information_schema`.`columns` col
|
||||
WHERE LOWER(col.table_schema) NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys');
|
||||
"""
|
||||
@@ -171,38 +169,7 @@ class Mysql(BaseSQLQueryRunner):
|
||||
if table_name not in schema:
|
||||
schema[table_name] = {"name": table_name, "columns": []}
|
||||
|
||||
schema[table_name]["columns"].append(
|
||||
{
|
||||
"name": row["column_name"],
|
||||
"type": row["data_type"],
|
||||
"description": row["column_comment"],
|
||||
}
|
||||
)
|
||||
|
||||
table_query = """
|
||||
SELECT col.table_schema as table_schema,
|
||||
col.table_name as table_name,
|
||||
col.table_comment as table_comment
|
||||
FROM `information_schema`.`tables` col
|
||||
WHERE LOWER(col.table_schema) NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys'); \
|
||||
"""
|
||||
|
||||
results, error = self.run_query(table_query, None)
|
||||
|
||||
if error is not None:
|
||||
self._handle_run_query_error(error)
|
||||
|
||||
for row in results["rows"]:
|
||||
if row["table_schema"] != self.configuration["db"]:
|
||||
table_name = "{}.{}".format(row["table_schema"], row["table_name"])
|
||||
else:
|
||||
table_name = row["table_name"]
|
||||
|
||||
if table_name not in schema:
|
||||
schema[table_name] = {"name": table_name, "columns": []}
|
||||
|
||||
if "table_comment" in row and row["table_comment"]:
|
||||
schema[table_name]["description"] = row["table_comment"]
|
||||
schema[table_name]["columns"].append(row["column_name"])
|
||||
|
||||
return list(schema.values())
|
||||
|
||||
|
||||
@@ -205,15 +205,24 @@ class PostgreSQL(BaseSQLQueryRunner):
|
||||
|
||||
def _get_tables(self, schema):
|
||||
"""
|
||||
relkind constants from https://www.postgresql.org/docs/current/catalog-pg-class.html
|
||||
relkind constants per https://www.postgresql.org/docs/10/static/catalog-pg-class.html
|
||||
r = regular table
|
||||
v = view
|
||||
m = materialized view
|
||||
f = foreign table
|
||||
p = partitioned table (new in 10)
|
||||
---
|
||||
i = index
|
||||
S = sequence
|
||||
t = TOAST table
|
||||
c = composite type
|
||||
"""
|
||||
|
||||
query = """
|
||||
SELECT s.nspname AS table_schema,
|
||||
c.relname AS table_name,
|
||||
a.attname AS column_name,
|
||||
NULL AS data_type
|
||||
SELECT s.nspname as table_schema,
|
||||
c.relname as table_name,
|
||||
a.attname as column_name,
|
||||
null as data_type
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace s
|
||||
ON c.relnamespace = s.oid
|
||||
@@ -222,7 +231,7 @@ class PostgreSQL(BaseSQLQueryRunner):
|
||||
ON a.attrelid = c.oid
|
||||
AND a.attnum > 0
|
||||
AND NOT a.attisdropped
|
||||
WHERE c.relkind = 'm'
|
||||
WHERE c.relkind IN ('m', 'f', 'p')
|
||||
AND has_table_privilege(s.nspname || '.' || c.relname, 'select')
|
||||
AND has_schema_privilege(s.nspname, 'usage')
|
||||
|
||||
@@ -234,8 +243,6 @@ class PostgreSQL(BaseSQLQueryRunner):
|
||||
data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
||||
AND has_table_privilege(table_schema || '.' || table_name, 'select')
|
||||
AND has_schema_privilege(table_schema, 'usage')
|
||||
"""
|
||||
|
||||
self._get_definitions(schema, query)
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
try:
|
||||
import snowflake.connector
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
|
||||
enabled = True
|
||||
except ImportError:
|
||||
enabled = False
|
||||
|
||||
|
||||
from base64 import b64decode
|
||||
|
||||
from redash import __version__
|
||||
from redash.query_runner import (
|
||||
TYPE_BOOLEAN,
|
||||
@@ -46,8 +43,6 @@ class Snowflake(BaseSQLQueryRunner):
|
||||
"account": {"type": "string"},
|
||||
"user": {"type": "string"},
|
||||
"password": {"type": "string"},
|
||||
"private_key_File": {"type": "string"},
|
||||
"private_key_pwd": {"type": "string"},
|
||||
"warehouse": {"type": "string"},
|
||||
"database": {"type": "string"},
|
||||
"region": {"type": "string", "default": "us-west"},
|
||||
@@ -62,15 +57,13 @@ class Snowflake(BaseSQLQueryRunner):
|
||||
"account",
|
||||
"user",
|
||||
"password",
|
||||
"private_key_File",
|
||||
"private_key_pwd",
|
||||
"warehouse",
|
||||
"database",
|
||||
"region",
|
||||
"host",
|
||||
],
|
||||
"required": ["user", "account", "database", "warehouse"],
|
||||
"secret": ["password", "private_key_File", "private_key_pwd"],
|
||||
"required": ["user", "password", "account", "database", "warehouse"],
|
||||
"secret": ["password"],
|
||||
"extra_options": [
|
||||
"host",
|
||||
],
|
||||
@@ -95,7 +88,7 @@ class Snowflake(BaseSQLQueryRunner):
|
||||
if region == "us-west":
|
||||
region = None
|
||||
|
||||
if self.configuration.get("host"):
|
||||
if self.configuration.__contains__("host"):
|
||||
host = self.configuration.get("host")
|
||||
else:
|
||||
if region:
|
||||
@@ -103,29 +96,14 @@ class Snowflake(BaseSQLQueryRunner):
|
||||
else:
|
||||
host = "{}.snowflakecomputing.com".format(account)
|
||||
|
||||
params = {
|
||||
"user": self.configuration["user"],
|
||||
"account": account,
|
||||
"region": region,
|
||||
"host": host,
|
||||
"application": "Redash/{} (Snowflake)".format(__version__.split("-")[0]),
|
||||
}
|
||||
|
||||
if self.configuration.get("password"):
|
||||
params["password"] = self.configuration["password"]
|
||||
elif self.configuration.get("private_key_File"):
|
||||
private_key_b64 = self.configuration.get("private_key_File")
|
||||
private_key_bytes = b64decode(private_key_b64)
|
||||
if self.configuration.get("private_key_pwd"):
|
||||
private_key_pwd = self.configuration.get("private_key_pwd").encode()
|
||||
else:
|
||||
private_key_pwd = None
|
||||
private_key_pem = load_pem_private_key(private_key_bytes, private_key_pwd)
|
||||
params["private_key"] = private_key_pem
|
||||
else:
|
||||
raise Exception("Neither password nor private_key_b64 is set.")
|
||||
|
||||
connection = snowflake.connector.connect(**params)
|
||||
connection = snowflake.connector.connect(
|
||||
user=self.configuration["user"],
|
||||
password=self.configuration["password"],
|
||||
account=account,
|
||||
region=region,
|
||||
host=host,
|
||||
application="Redash/{} (Snowflake)".format(__version__.split("-")[0]),
|
||||
)
|
||||
|
||||
return connection
|
||||
|
||||
|
||||
@@ -82,19 +82,9 @@ class QuerySerializer(Serializer):
|
||||
else:
|
||||
result = [serialize_query(query, **self.options) for query in self.object_or_list]
|
||||
if self.options.get("with_favorite_state", True):
|
||||
queries = list(self.object_or_list)
|
||||
favorites = models.Favorite.query.filter(
|
||||
models.Favorite.object_id.in_([o.id for o in queries]),
|
||||
models.Favorite.object_type == "Query",
|
||||
models.Favorite.user_id == current_user.id,
|
||||
)
|
||||
favorites_dict = {fav.object_id: fav for fav in favorites}
|
||||
|
||||
favorite_ids = models.Favorite.are_favorites(current_user.id, self.object_or_list)
|
||||
for query in result:
|
||||
favorite = favorites_dict.get(query["id"])
|
||||
query["is_favorite"] = favorite is not None
|
||||
if favorite:
|
||||
query["starred_at"] = favorite.created_at
|
||||
query["is_favorite"] = query["id"] in favorite_ids
|
||||
|
||||
return result
|
||||
|
||||
@@ -273,19 +263,9 @@ class DashboardSerializer(Serializer):
|
||||
else:
|
||||
result = [serialize_dashboard(obj, **self.options) for obj in self.object_or_list]
|
||||
if self.options.get("with_favorite_state", True):
|
||||
dashboards = list(self.object_or_list)
|
||||
favorites = models.Favorite.query.filter(
|
||||
models.Favorite.object_id.in_([o.id for o in dashboards]),
|
||||
models.Favorite.object_type == "Dashboard",
|
||||
models.Favorite.user_id == current_user.id,
|
||||
)
|
||||
favorites_dict = {fav.object_id: fav for fav in favorites}
|
||||
|
||||
for query in result:
|
||||
favorite = favorites_dict.get(query["id"])
|
||||
query["is_favorite"] = favorite is not None
|
||||
if favorite:
|
||||
query["starred_at"] = favorite.created_at
|
||||
favorite_ids = models.Favorite.are_favorites(current_user.id, self.object_or_list)
|
||||
for obj in result:
|
||||
obj["is_favorite"] = obj["id"] in favorite_ids
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class TestBigQueryQueryRunner(unittest.TestCase):
|
||||
query = "SELECT a FROM tbl"
|
||||
expect = (
|
||||
"/* Username: username, query_id: adhoc, "
|
||||
"Query Hash: query-hash, "
|
||||
"Job ID: job-id, Query Hash: query-hash, "
|
||||
"Scheduled: False */ SELECT a FROM tbl"
|
||||
)
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"@types/jest": "^26.0.18",
|
||||
"@types/leaflet": "^1.5.19",
|
||||
"@types/numeral": "0.0.28",
|
||||
"@types/plotly.js": "^3.0.3",
|
||||
"@types/plotly.js": "^2.35.2",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/tinycolor2": "^1.4.2",
|
||||
@@ -91,7 +91,7 @@
|
||||
"leaflet.markercluster": "^1.1.0",
|
||||
"lodash": "^4.17.10",
|
||||
"numeral": "^2.0.6",
|
||||
"plotly.js": "3.1.0",
|
||||
"plotly.js": "2.35.3",
|
||||
"react-pivottable": "^0.9.0",
|
||||
"react-sortable-hoc": "^1.10.1",
|
||||
"tinycolor2": "^1.4.1",
|
||||
|
||||
@@ -336,7 +336,7 @@ export default function GeneralSettings({ options, data, onOptionsChange }: any)
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{!includes(["custom", "heatmap", "bubble"], options.globalSeriesType) && (
|
||||
{!includes(["custom", "heatmap", "bubble", "scatter"], options.globalSeriesType) && (
|
||||
// @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message
|
||||
<Section>
|
||||
<Select
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"series": [
|
||||
{
|
||||
"visible": true,
|
||||
"offsetgroup": "0",
|
||||
"type": "bar",
|
||||
"name": "a",
|
||||
"x": ["x1", "x2", "x3", "x4"],
|
||||
@@ -63,6 +64,7 @@
|
||||
},
|
||||
{
|
||||
"visible": true,
|
||||
"offsetgroup": "1",
|
||||
"type": "bar",
|
||||
"name": "b",
|
||||
"x": ["x1", "x2", "x3", "x4"],
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"series": [
|
||||
{
|
||||
"visible": true,
|
||||
"offsetgroup": "0",
|
||||
"type": "bar",
|
||||
"name": "a",
|
||||
"x": ["x1", "x2", "x3", "x4"],
|
||||
@@ -63,6 +64,7 @@
|
||||
},
|
||||
{
|
||||
"visible": true,
|
||||
"offsetgroup": "1",
|
||||
"type": "bar",
|
||||
"name": "b",
|
||||
"x": ["x1", "x2", "x3", "x4"],
|
||||
|
||||
@@ -8,30 +8,10 @@ import updateAxes from "./updateAxes";
|
||||
import updateChartSize from "./updateChartSize";
|
||||
import { prepareCustomChartData, createCustomChartRenderer } from "./customChartUtils";
|
||||
|
||||
const rangeSliderIcon = {
|
||||
'width': 400,
|
||||
'height': 400,
|
||||
'path': 'M50 180h300a20 20 0 0 1 0 40H50a20 20 0 0 1 0-40z M160 200a40 40 0 1 0 -80 0a40 40 0 1 0 80 0 M320 200a40 40 0 1 0 -80 0a40 40 0 1 0 80 0',
|
||||
};
|
||||
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'setPlotConfig' does not exist on type 't... Remove this comment to see the full error message
|
||||
Plotly.setPlotConfig({
|
||||
modeBarButtonsToRemove: ["sendDataToCloud"],
|
||||
modeBarButtonsToAdd: ["togglespikelines", "v1hovermode",
|
||||
{
|
||||
name: 'toggleRangeslider',
|
||||
title: 'Toggle rangeslider',
|
||||
icon: rangeSliderIcon,
|
||||
click: function(gd: any) {
|
||||
if(gd?.layout?.xaxis) {
|
||||
let newRangeslider: any = {};
|
||||
if (gd.layout.xaxis?.rangeslider) {
|
||||
newRangeslider = null;
|
||||
}
|
||||
(Plotly.relayout as any)(gd, 'xaxis.rangeslider', newRangeslider);
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
modeBarButtonsToAdd: ["togglespikelines", "v1hovermode"],
|
||||
locale: window.navigator.language,
|
||||
});
|
||||
|
||||
|
||||
@@ -26,9 +26,7 @@ function getHoverInfoPattern(options: any) {
|
||||
|
||||
function prepareBarSeries(series: any, options: any, additionalOptions: any) {
|
||||
series.type = "bar";
|
||||
if (!options.series.stacking) {
|
||||
series.offsetgroup = toString(additionalOptions.index);
|
||||
}
|
||||
series.offsetgroup = toString(additionalOptions.index);
|
||||
if (options.showDataLabels) {
|
||||
series.textposition = "inside";
|
||||
} else {
|
||||
@@ -96,10 +94,7 @@ function prepareSeries(series: any, options: any, numSeries: any, additionalOpti
|
||||
// For bubble/scatter charts `y` may be any (similar to `x`) - numeric is only bubble size;
|
||||
// for other types `y` is always number
|
||||
const cleanYValue = includes(["bubble", "scatter"], seriesOptions.type)
|
||||
? (v: any, axixType: any) => {
|
||||
v = normalizeValue(v, axixType);
|
||||
return includes(["scatter"], seriesOptions.type) && options.missingValuesAsZero && isNil(v) ? 0.0 : v;
|
||||
}
|
||||
? normalizeValue
|
||||
: (v: any) => {
|
||||
v = cleanNumber(v);
|
||||
return options.missingValuesAsZero && isNil(v) ? 0.0 : v;
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function initTextColumn(column: any) {
|
||||
function TextColumn({ row }: any) {
|
||||
// eslint-disable-line react/prop-types
|
||||
const { text } = prepareData(row);
|
||||
return (column.allowHTML && typeof text === 'string') ? <HtmlContent>{text}</HtmlContent> : text;
|
||||
return column.allowHTML ? <HtmlContent>{text}</HtmlContent> : text;
|
||||
}
|
||||
|
||||
TextColumn.prepareData = prepareData;
|
||||
|
||||
@@ -1776,11 +1776,6 @@
|
||||
parse-rect "^1.2.0"
|
||||
pick-by-alias "^1.2.0"
|
||||
|
||||
"@plotly/regl@^2.1.2":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@plotly/regl/-/regl-2.1.2.tgz#fd31e3e820ed8824d59a67ab5e766bb101b810b6"
|
||||
integrity sha512-Mdk+vUACbQvjd0m/1JJjOOafmkp/EpmHjISsopEz5Av44CBq7rPC05HHNbYGKVyNUF2zmEoBS/TT0pd0SPFFyw==
|
||||
|
||||
"@ts-morph/bootstrap@^0.16.0":
|
||||
version "0.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@ts-morph/bootstrap/-/bootstrap-0.16.0.tgz#c97034175a8fc2b7d3f575526d819877f7ed2d83"
|
||||
@@ -2234,10 +2229,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.5.tgz#a9495a58d8c75be4ffe9a0bd749a307715c07404"
|
||||
integrity sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==
|
||||
|
||||
"@types/plotly.js@^3.0.3":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/plotly.js/-/plotly.js-3.0.3.tgz#ed72b67ce65adc22b2bc75da845e973335ea7234"
|
||||
integrity sha512-9CENH8hh2diOML3o4lEd4H0nwQ4uECEE9mZQc+zriGEdd0zK8ru75t7qFhaMQmiWFFPGWqI4FpodBZFTmWpdbQ==
|
||||
"@types/plotly.js@^2.35.2":
|
||||
version "2.35.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/plotly.js/-/plotly.js-2.35.2.tgz#c771a7bf56b398730f8c799879895de4294289c0"
|
||||
integrity sha512-tn0Kp7F6VWiu96jknCvR/PcdIGIATeIK+Z5WXH3bEvG6CRwUNfhy34yBhfPYmTea7mMQxXvTZKGMm6/Y4wxESg==
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.3"
|
||||
@@ -3304,11 +3299,6 @@ color-name@1.1.3, color-name@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
color-name@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-2.0.0.tgz#03ff6b1b5aec9bb3cf1ed82400c2790dfcd01d2d"
|
||||
integrity sha512-SbtvAMWvASO5TE2QP07jHBMXKafgdZz8Vrsrn96fiL+O92/FN/PLARzUW5sKt013fjAprK2d2iCn2hk2Xb5oow==
|
||||
|
||||
color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
@@ -3339,22 +3329,7 @@ color-parse@^1.3.8:
|
||||
defined "^1.0.0"
|
||||
is-plain-obj "^1.1.0"
|
||||
|
||||
color-parse@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/color-parse/-/color-parse-2.0.2.tgz#37b46930424924060988edf25b24e6ffb4a1dc3f"
|
||||
integrity sha512-eCtOz5w5ttWIUcaKLiktF+DxZO1R9KLNY/xhbV6CkhM7sR3GhVghmt6X6yOnzeaM24po+Z9/S1apbXMwA3Iepw==
|
||||
dependencies:
|
||||
color-name "^2.0.0"
|
||||
|
||||
color-rgba@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/color-rgba/-/color-rgba-3.0.0.tgz#77090bdcdb2951c1735e20099ddd50401675375b"
|
||||
integrity sha512-PPwZYkEY3M2THEHHV6Y95sGUie77S7X8v+h1r6LSAPF3/LL2xJ8duUXSrkic31Nzc4odPwHgUbiX/XuTYzQHQg==
|
||||
dependencies:
|
||||
color-parse "^2.0.0"
|
||||
color-space "^2.0.0"
|
||||
|
||||
color-rgba@^2.1.1:
|
||||
color-rgba@2.1.1, color-rgba@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/color-rgba/-/color-rgba-2.1.1.tgz#4633b83817c7406c90b3d7bf4d1acfa48dde5c83"
|
||||
integrity sha512-VaX97wsqrMwLSOR6H7rU1Doa2zyVdmShabKrPEIFywLlHoibgD3QW9Dw6fSqM4+H/LfjprDNAUUW31qEQcGzNw==
|
||||
@@ -3371,11 +3346,6 @@ color-space@^1.14.6:
|
||||
hsluv "^0.0.3"
|
||||
mumath "^3.3.4"
|
||||
|
||||
color-space@^2.0.0:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/color-space/-/color-space-2.3.2.tgz#d8c72bab09ef26b98abebc58bc1586ce3073033d"
|
||||
integrity sha512-BcKnbOEsOarCwyoLstcoEztwT0IJxqqQkNwDuA3a65sICvvHL2yoeV13psoDFh5IuiOMnIOKdQDwB4Mk3BypiA==
|
||||
|
||||
colorette@^2.0.14:
|
||||
version "2.0.20"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
|
||||
@@ -3562,6 +3532,20 @@ css-loader@^3.5.2:
|
||||
schema-utils "^2.6.5"
|
||||
semver "^6.3.0"
|
||||
|
||||
css-loader@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.2.tgz#64671541c6efe06b0e22e750503106bdd86880f8"
|
||||
integrity sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==
|
||||
dependencies:
|
||||
icss-utils "^5.1.0"
|
||||
postcss "^8.4.33"
|
||||
postcss-modules-extract-imports "^3.1.0"
|
||||
postcss-modules-local-by-default "^4.0.5"
|
||||
postcss-modules-scope "^3.2.0"
|
||||
postcss-modules-values "^4.0.0"
|
||||
postcss-value-parser "^4.2.0"
|
||||
semver "^7.5.4"
|
||||
|
||||
css-select@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
|
||||
@@ -5353,6 +5337,11 @@ icss-utils@^4.0.0, icss-utils@^4.1.1:
|
||||
dependencies:
|
||||
postcss "^7.0.14"
|
||||
|
||||
icss-utils@^5.0.0, icss-utils@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
|
||||
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
|
||||
|
||||
ieee754@^1.1.12:
|
||||
version "1.1.13"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
|
||||
@@ -6704,7 +6693,7 @@ map-visit@^1.0.0:
|
||||
dependencies:
|
||||
object-visit "^1.0.0"
|
||||
|
||||
maplibre-gl@^4.7.1:
|
||||
maplibre-gl@^4.5.2:
|
||||
version "4.7.1"
|
||||
resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-4.7.1.tgz#06a524438ee2aafbe8bcd91002a4e01468ea5486"
|
||||
integrity sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==
|
||||
@@ -6907,6 +6896,11 @@ nan@^2.12.1:
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
|
||||
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
|
||||
|
||||
nanoid@^3.3.8:
|
||||
version "3.3.8"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
|
||||
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
|
||||
|
||||
nanomatch@^1.2.9:
|
||||
version "1.2.13"
|
||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
||||
@@ -7453,6 +7447,11 @@ picocolors@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
|
||||
integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
|
||||
|
||||
picocolors@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
@@ -7506,16 +7505,15 @@ pkg-up@^3.1.0:
|
||||
dependencies:
|
||||
find-up "^3.0.0"
|
||||
|
||||
plotly.js@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/plotly.js/-/plotly.js-3.1.0.tgz#a095a37d0f1b04bb0e9686853df54a4e6437af2e"
|
||||
integrity sha512-vx+CyzApL9tquFpwoPHOGSIWDbFPsA4om/tXZcnsygGUejXideDF9R5VwkltEIDG7Xuof45quVPyz1otv6Aqjw==
|
||||
plotly.js@2.35.3:
|
||||
version "2.35.3"
|
||||
resolved "https://registry.yarnpkg.com/plotly.js/-/plotly.js-2.35.3.tgz#6a7787d63b4d334948c281aa9c8df7fb941b425e"
|
||||
integrity sha512-7RaC6FxmCUhpD6H4MpD+QLUu3hCn76I11rotRefrh3m1iDvWqGnVqVk9dSaKmRAhFD3vsNsYea0OxnR1rc2IzQ==
|
||||
dependencies:
|
||||
"@plotly/d3" "3.8.2"
|
||||
"@plotly/d3-sankey" "0.7.2"
|
||||
"@plotly/d3-sankey-circular" "0.33.1"
|
||||
"@plotly/mapbox-gl" "1.13.4"
|
||||
"@plotly/regl" "^2.1.2"
|
||||
"@turf/area" "^7.1.0"
|
||||
"@turf/bbox" "^7.1.0"
|
||||
"@turf/centroid" "^7.1.0"
|
||||
@@ -7524,8 +7522,9 @@ plotly.js@3.1.0:
|
||||
color-alpha "1.0.4"
|
||||
color-normalize "1.5.0"
|
||||
color-parse "2.0.0"
|
||||
color-rgba "3.0.0"
|
||||
color-rgba "2.1.1"
|
||||
country-regex "^1.1.0"
|
||||
css-loader "^7.1.2"
|
||||
d3-force "^1.2.1"
|
||||
d3-format "^1.4.5"
|
||||
d3-geo "^1.12.1"
|
||||
@@ -7540,7 +7539,7 @@ plotly.js@3.1.0:
|
||||
has-hover "^1.0.1"
|
||||
has-passive-events "^1.0.0"
|
||||
is-mobile "^4.0.0"
|
||||
maplibre-gl "^4.7.1"
|
||||
maplibre-gl "^4.5.2"
|
||||
mouse-change "^1.4.0"
|
||||
mouse-event-offset "^3.0.2"
|
||||
mouse-wheel "^1.2.0"
|
||||
@@ -7549,18 +7548,20 @@ plotly.js@3.1.0:
|
||||
point-in-polygon "^1.1.0"
|
||||
polybooljs "^1.2.2"
|
||||
probe-image-size "^7.2.3"
|
||||
regl "npm:@plotly/regl@^2.1.2"
|
||||
regl-error2d "^2.0.12"
|
||||
regl-line2d "^3.1.3"
|
||||
regl-scatter2d "^3.3.1"
|
||||
regl-splom "^1.0.14"
|
||||
strongly-connected-components "^1.0.1"
|
||||
style-loader "^4.0.0"
|
||||
superscript-text "^1.0.0"
|
||||
svg-path-sdf "^1.1.3"
|
||||
tinycolor2 "^1.4.2"
|
||||
to-px "1.0.1"
|
||||
topojson-client "^3.1.0"
|
||||
webgl-context "^2.2.0"
|
||||
world-calendars "^1.0.4"
|
||||
world-calendars "^1.0.3"
|
||||
|
||||
pn@^1.1.0:
|
||||
version "1.1.0"
|
||||
@@ -7589,6 +7590,11 @@ postcss-modules-extract-imports@^2.0.0:
|
||||
dependencies:
|
||||
postcss "^7.0.5"
|
||||
|
||||
postcss-modules-extract-imports@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002"
|
||||
integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==
|
||||
|
||||
postcss-modules-local-by-default@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915"
|
||||
@@ -7599,6 +7605,15 @@ postcss-modules-local-by-default@^3.0.2:
|
||||
postcss-selector-parser "^6.0.2"
|
||||
postcss-value-parser "^4.0.0"
|
||||
|
||||
postcss-modules-local-by-default@^4.0.5:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz#d150f43837831dae25e4085596e84f6f5d6ec368"
|
||||
integrity sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==
|
||||
dependencies:
|
||||
icss-utils "^5.0.0"
|
||||
postcss-selector-parser "^7.0.0"
|
||||
postcss-value-parser "^4.1.0"
|
||||
|
||||
postcss-modules-scope@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee"
|
||||
@@ -7607,6 +7622,13 @@ postcss-modules-scope@^2.2.0:
|
||||
postcss "^7.0.6"
|
||||
postcss-selector-parser "^6.0.0"
|
||||
|
||||
postcss-modules-scope@^3.2.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz#1bbccddcb398f1d7a511e0a2d1d047718af4078c"
|
||||
integrity sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==
|
||||
dependencies:
|
||||
postcss-selector-parser "^7.0.0"
|
||||
|
||||
postcss-modules-values@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10"
|
||||
@@ -7615,6 +7637,13 @@ postcss-modules-values@^3.0.0:
|
||||
icss-utils "^4.0.0"
|
||||
postcss "^7.0.6"
|
||||
|
||||
postcss-modules-values@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
|
||||
integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
|
||||
dependencies:
|
||||
icss-utils "^5.0.0"
|
||||
|
||||
postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
|
||||
@@ -7624,6 +7653,14 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
|
||||
indexes-of "^1.0.1"
|
||||
uniq "^1.0.1"
|
||||
|
||||
postcss-selector-parser@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz#4d6af97eba65d73bc4d84bcb343e865d7dd16262"
|
||||
integrity sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-value-parser@^3.2.3:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
|
||||
@@ -7634,6 +7671,11 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.3:
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d"
|
||||
integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==
|
||||
|
||||
postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@^6.0.22, postcss@^6.0.23:
|
||||
version "6.0.23"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
|
||||
@@ -7652,6 +7694,15 @@ postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.27, postcss@^7.0.5, postcss@^7.0.
|
||||
source-map "^0.6.1"
|
||||
supports-color "^6.1.0"
|
||||
|
||||
postcss@^8.4.33:
|
||||
version "8.5.3"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb"
|
||||
integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==
|
||||
dependencies:
|
||||
nanoid "^3.3.8"
|
||||
picocolors "^1.1.1"
|
||||
source-map-js "^1.2.1"
|
||||
|
||||
potpack@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.1.tgz#d1b1afd89e4c8f7762865ec30bd112ab767e2ebf"
|
||||
@@ -8180,6 +8231,11 @@ regl@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/regl/-/regl-2.1.1.tgz#fb3eecbc684031ec6172f68aaab2cbe9c3aa3148"
|
||||
integrity sha512-+IOGrxl3FZ8ZM9ixCWQZzFRiRn7Rzn9bu3iFHwg/yz4tlOUQgbO4PHLgG+1ZT60zcIV8tief6Qrmyl8qcoJP0g==
|
||||
|
||||
"regl@npm:@plotly/regl@^2.1.2":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@plotly/regl/-/regl-2.1.2.tgz#fd31e3e820ed8824d59a67ab5e766bb101b810b6"
|
||||
integrity sha512-Mdk+vUACbQvjd0m/1JJjOOafmkp/EpmHjISsopEz5Av44CBq7rPC05HHNbYGKVyNUF2zmEoBS/TT0pd0SPFFyw==
|
||||
|
||||
remove-trailing-separator@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||
@@ -8503,6 +8559,11 @@ semver@^7.2.1:
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^7.5.4:
|
||||
version "7.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
|
||||
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
|
||||
|
||||
serialize-javascript@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c"
|
||||
@@ -8644,6 +8705,11 @@ sortablejs@^1.6.1:
|
||||
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290"
|
||||
integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==
|
||||
|
||||
source-map-js@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||
|
||||
source-map-resolve@^0.5.0:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
|
||||
@@ -8934,6 +9000,11 @@ style-loader@^3.3.3:
|
||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.3.tgz#bba8daac19930169c0c9c96706749a597ae3acff"
|
||||
integrity sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==
|
||||
|
||||
style-loader@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-4.0.0.tgz#0ea96e468f43c69600011e0589cb05c44f3b17a5"
|
||||
integrity sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==
|
||||
|
||||
supercluster@^7.1.0:
|
||||
version "7.1.5"
|
||||
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.5.tgz#65a6ce4a037a972767740614c19051b64b8be5a3"
|
||||
@@ -9492,7 +9563,7 @@ use@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
||||
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
|
||||
|
||||
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
@@ -9748,10 +9819,10 @@ word-wrap@~1.2.3:
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
||||
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
||||
|
||||
world-calendars@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/world-calendars/-/world-calendars-1.0.4.tgz#2a12bcbd796b6c99aef2e52f281229faad8fa96c"
|
||||
integrity sha512-VGRnLJS+xJmGDPodgJRnGIDwGu0s+Cr9V2HB3EzlDZ5n0qb8h5SJtGUEkjrphZYAglEiXZ6kiXdmk0H/h/uu/w==
|
||||
world-calendars@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/world-calendars/-/world-calendars-1.0.3.tgz#b25c5032ba24128ffc41d09faf4a5ec1b9c14335"
|
||||
integrity sha1-slxQMrokEo/8QdCfr0pewbnBQzU=
|
||||
dependencies:
|
||||
object-assign "^4.1.0"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const webpack = require("webpack");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const WebpackBuildNotifierPlugin = require("webpack-build-notifier");
|
||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||
const ManifestPlugin = require("webpack-manifest-plugin");
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const LessPluginAutoPrefix = require("less-plugin-autoprefix");
|
||||
@@ -76,6 +76,8 @@ const config = {
|
||||
publicPath: staticPath
|
||||
},
|
||||
node: {
|
||||
fs: "empty",
|
||||
path: "empty"
|
||||
},
|
||||
resolve: {
|
||||
symlinks: false,
|
||||
@@ -83,14 +85,6 @@ const config = {
|
||||
alias: {
|
||||
"@": appPath,
|
||||
extensions: extensionPath
|
||||
},
|
||||
fallback: {
|
||||
fs: false,
|
||||
url: require.resolve("url/"),
|
||||
stream: require.resolve("stream-browserify"),
|
||||
assert: require.resolve("assert/"),
|
||||
util: require.resolve("util/"),
|
||||
process: require.resolve("process/browser"),
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
@@ -115,7 +109,7 @@ const config = {
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "[name].[chunkhash].css"
|
||||
}),
|
||||
new WebpackManifestPlugin({
|
||||
new ManifestPlugin({
|
||||
fileName: "asset-manifest.json",
|
||||
publicPath: ""
|
||||
}),
|
||||
@@ -128,13 +122,7 @@ const config = {
|
||||
{ from: "client/app/assets/fonts", to: "fonts/" }
|
||||
],
|
||||
}),
|
||||
isHotReloadingEnabled && new ReactRefreshWebpackPlugin({ overlay: false }),
|
||||
new webpack.ProvidePlugin({
|
||||
// Make a global `process` variable that points to the `process` package,
|
||||
// because the `util` package expects there to be a global variable named `process`.
|
||||
// Thanks to https://stackoverflow.com/a/65018686/14239942
|
||||
process: 'process/browser'
|
||||
})
|
||||
isHotReloadingEnabled && new ReactRefreshWebpackPlugin({ overlay: false })
|
||||
].filter(Boolean),
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
@@ -149,12 +137,6 @@ const config = {
|
||||
test: /\.js$/,
|
||||
enforce: "pre",
|
||||
use: ["source-map-loader"],
|
||||
resolve: {
|
||||
fullySpecified: false
|
||||
},
|
||||
exclude: [
|
||||
/node_modules\/@plotly\/mapbox-gl/,
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(t|j)sx?$/,
|
||||
@@ -251,7 +233,7 @@ const config = {
|
||||
}
|
||||
]
|
||||
},
|
||||
devtool: isProduction ? "source-map" : "eval-cheap-module-source-map",
|
||||
devtool: isProduction ? "source-map" : "cheap-eval-module-source-map",
|
||||
stats: {
|
||||
children: false,
|
||||
modules: false,
|
||||
|
||||
Reference in New Issue
Block a user