mirror of
https://github.com/apache/superset.git
synced 2024-09-16 02:29:39 -04:00
refactor: Introduce api resource hooks, fetch owners for chart errors (#13218)
* refactor: introduce api resource hooks, fix error owners
* comment
* vestigial comment
* not a function
* clarification
* vestigial comment
* filter the api for a leaner response
* reorganize code, add resource hook catalog
* better names
* implement state management in redux
* Revert "implement state management in redux"
This reverts commit d64d50a99f
.
* add tests for hooks
This commit is contained in:
parent
72721845e1
commit
e11d0cbf44
@ -25,9 +25,9 @@ import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
|||||||
import { Logger, LOG_ACTIONS_RENDER_CHART } from '../logger/LogUtils';
|
import { Logger, LOG_ACTIONS_RENDER_CHART } from '../logger/LogUtils';
|
||||||
import Loading from '../components/Loading';
|
import Loading from '../components/Loading';
|
||||||
import RefreshChartOverlay from '../components/RefreshChartOverlay';
|
import RefreshChartOverlay from '../components/RefreshChartOverlay';
|
||||||
import ErrorMessageWithStackTrace from '../components/ErrorMessage/ErrorMessageWithStackTrace';
|
|
||||||
import ErrorBoundary from '../components/ErrorBoundary';
|
import ErrorBoundary from '../components/ErrorBoundary';
|
||||||
import ChartRenderer from './ChartRenderer';
|
import ChartRenderer from './ChartRenderer';
|
||||||
|
import { ChartErrorMessage } from './ChartErrorMessage';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
annotationData: PropTypes.object,
|
annotationData: PropTypes.object,
|
||||||
@ -49,9 +49,6 @@ const propTypes = {
|
|||||||
timeout: PropTypes.number,
|
timeout: PropTypes.number,
|
||||||
vizType: PropTypes.string.isRequired,
|
vizType: PropTypes.string.isRequired,
|
||||||
triggerRender: PropTypes.bool,
|
triggerRender: PropTypes.bool,
|
||||||
owners: PropTypes.arrayOf(
|
|
||||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
||||||
),
|
|
||||||
// state
|
// state
|
||||||
chartAlert: PropTypes.string,
|
chartAlert: PropTypes.string,
|
||||||
chartStatus: PropTypes.string,
|
chartStatus: PropTypes.string,
|
||||||
@ -152,17 +149,13 @@ class Chart extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderErrorMessage(queryResponse) {
|
renderErrorMessage(queryResponse) {
|
||||||
const { chartAlert, chartStackTrace, dashboardId, owners } = this.props;
|
const { chartId, chartAlert, chartStackTrace, dashboardId } = this.props;
|
||||||
|
|
||||||
const error = queryResponse?.errors?.[0];
|
const error = queryResponse?.errors?.[0];
|
||||||
if (error) {
|
|
||||||
const extra = error.extra || {};
|
|
||||||
extra.owners = owners;
|
|
||||||
error.extra = extra;
|
|
||||||
}
|
|
||||||
const message = chartAlert || queryResponse?.message;
|
const message = chartAlert || queryResponse?.message;
|
||||||
return (
|
return (
|
||||||
<ErrorMessageWithStackTrace
|
<ChartErrorMessage
|
||||||
|
chartId={chartId}
|
||||||
error={error}
|
error={error}
|
||||||
subtitle={message}
|
subtitle={message}
|
||||||
copyText={message}
|
copyText={message}
|
||||||
|
47
superset-frontend/src/chart/ChartErrorMessage.tsx
Normal file
47
superset-frontend/src/chart/ChartErrorMessage.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useChartOwnerNames } from 'src/common/hooks/apiResources';
|
||||||
|
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||||
|
import { SupersetError } from 'src/components/ErrorMessage/types';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
chartId: string;
|
||||||
|
error?: SupersetError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetches the chart owners and adds them to the extra data of the error message
|
||||||
|
*/
|
||||||
|
export const ChartErrorMessage: React.FC<Props> = ({
|
||||||
|
chartId,
|
||||||
|
error,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const { result: owners } = useChartOwnerNames(chartId);
|
||||||
|
|
||||||
|
// don't mutate props
|
||||||
|
const ownedError = error && {
|
||||||
|
...error,
|
||||||
|
extra: { ...error.extra, owners },
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ErrorMessageWithStackTrace {...props} error={ownedError} />;
|
||||||
|
};
|
@ -0,0 +1,165 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { makeApi } from '@superset-ui/core';
|
||||||
|
import { act, renderHook } from '@testing-library/react-hooks';
|
||||||
|
import {
|
||||||
|
ResourceStatus,
|
||||||
|
useApiResourceFullBody,
|
||||||
|
useApiV1Resource,
|
||||||
|
useTransformedResource,
|
||||||
|
} from './apiResources';
|
||||||
|
|
||||||
|
const fakeApiResult = {
|
||||||
|
id: 1,
|
||||||
|
name: 'fake api result',
|
||||||
|
};
|
||||||
|
|
||||||
|
const nameToAllCaps = (thing: any) => ({
|
||||||
|
...thing,
|
||||||
|
name: thing.name.toUpperCase(),
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('@superset-ui/core', () => ({
|
||||||
|
...jest.requireActual<any>('@superset-ui/core'),
|
||||||
|
makeApi: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(jest.fn().mockResolvedValue(fakeApiResult)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('apiResource hooks', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useApiResourceFullBody', () => {
|
||||||
|
it('returns a loading state at the start', async () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useApiResourceFullBody('/test/endpoint'),
|
||||||
|
);
|
||||||
|
expect(result.current).toEqual({
|
||||||
|
status: ResourceStatus.LOADING,
|
||||||
|
result: null,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves to the value from the api', async () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useApiResourceFullBody('/test/endpoint'),
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
expect(result.current).toEqual({
|
||||||
|
status: ResourceStatus.COMPLETE,
|
||||||
|
result: fakeApiResult,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles api errors', async () => {
|
||||||
|
const fakeError = new Error('fake api error');
|
||||||
|
(makeApi as any).mockReturnValue(jest.fn().mockRejectedValue(fakeError));
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useApiResourceFullBody('/test/endpoint'),
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
expect(result.current).toEqual({
|
||||||
|
status: ResourceStatus.ERROR,
|
||||||
|
result: null,
|
||||||
|
error: fakeError,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useTransformedResource', () => {
|
||||||
|
it('applies a transformation to the resource', () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useTransformedResource(
|
||||||
|
{
|
||||||
|
status: ResourceStatus.COMPLETE,
|
||||||
|
result: fakeApiResult,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
nameToAllCaps,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(result.current).toEqual({
|
||||||
|
status: ResourceStatus.COMPLETE,
|
||||||
|
result: {
|
||||||
|
id: 1,
|
||||||
|
name: 'FAKE API RESULT',
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works while loading', () => {
|
||||||
|
const nameToAllCaps = (thing: any) => ({
|
||||||
|
...thing,
|
||||||
|
name: thing.name.toUpperCase(),
|
||||||
|
});
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useTransformedResource(
|
||||||
|
{
|
||||||
|
status: ResourceStatus.LOADING,
|
||||||
|
result: null,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
nameToAllCaps,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(result.current).toEqual({
|
||||||
|
status: ResourceStatus.LOADING,
|
||||||
|
result: null,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useApiV1Endpoint', () => {
|
||||||
|
it('resolves to the value from the api', async () => {
|
||||||
|
(makeApi as any).mockReturnValue(
|
||||||
|
jest.fn().mockResolvedValue({
|
||||||
|
meta: 'data',
|
||||||
|
count: 1,
|
||||||
|
result: fakeApiResult,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const { result } = renderHook(() => useApiV1Resource('/test/endpoint'));
|
||||||
|
await act(async () => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
expect(result.current).toEqual({
|
||||||
|
status: ResourceStatus.COMPLETE,
|
||||||
|
result: fakeApiResult,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
181
superset-frontend/src/common/hooks/apiResources/apiResources.ts
Normal file
181
superset-frontend/src/common/hooks/apiResources/apiResources.ts
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { makeApi } from '@superset-ui/core';
|
||||||
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
export enum ResourceStatus {
|
||||||
|
LOADING = 'loading',
|
||||||
|
COMPLETE = 'complete',
|
||||||
|
ERROR = 'error',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object containing the data fetched from the API,
|
||||||
|
* as well as loading and error info
|
||||||
|
*/
|
||||||
|
export type Resource<T> = LoadingState | CompleteState<T> | ErrorState;
|
||||||
|
|
||||||
|
// Trying out something a little different: a separate type per status.
|
||||||
|
// This should let Typescript know whether a Resource has a result or error.
|
||||||
|
// It's possible that I'm expecting too much from Typescript here.
|
||||||
|
// If this ends up causing problems, we can change the type to:
|
||||||
|
//
|
||||||
|
// export type Resource<T> = {
|
||||||
|
// status: ResourceStatus;
|
||||||
|
// result: null | T;
|
||||||
|
// error: null | Error;
|
||||||
|
// }
|
||||||
|
|
||||||
|
type LoadingState = {
|
||||||
|
status: ResourceStatus.LOADING;
|
||||||
|
result: null;
|
||||||
|
error: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CompleteState<T> = {
|
||||||
|
status: ResourceStatus.COMPLETE;
|
||||||
|
result: T;
|
||||||
|
error: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ErrorState = {
|
||||||
|
status: ResourceStatus.ERROR;
|
||||||
|
result: null;
|
||||||
|
error: Error;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialState: LoadingState = {
|
||||||
|
status: ResourceStatus.LOADING,
|
||||||
|
result: null,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A general-purpose hook to fetch the response from an endpoint.
|
||||||
|
* Returns the full response body from the API, including metadata.
|
||||||
|
*
|
||||||
|
* Note: You likely want {useApiV1Resource} instead of this!
|
||||||
|
*
|
||||||
|
* TODO Store the state in redux or something, share state between hook instances.
|
||||||
|
*
|
||||||
|
* TODO Include a function in the returned resource object to refresh the data.
|
||||||
|
*
|
||||||
|
* A core design decision here is composition > configuration,
|
||||||
|
* and every hook should only have one job.
|
||||||
|
* Please address new needs with new hooks if possible,
|
||||||
|
* rather than adding config options to this hook.
|
||||||
|
*
|
||||||
|
* @param endpoint The url where the resource is located.
|
||||||
|
*/
|
||||||
|
export function useApiResourceFullBody<RESULT>(
|
||||||
|
endpoint: string,
|
||||||
|
): Resource<RESULT> {
|
||||||
|
const [resource, setResource] = useState<Resource<RESULT>>(initialState);
|
||||||
|
const cancelRef = useRef<() => void>(() => {});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// If refresh is implemented, this will need to change.
|
||||||
|
// The previous values should stay during refresh.
|
||||||
|
setResource(initialState);
|
||||||
|
|
||||||
|
// when this effect runs, the endpoint has changed.
|
||||||
|
// cancel any current calls so that state doesn't get messed up.
|
||||||
|
cancelRef.current();
|
||||||
|
let cancelled = false;
|
||||||
|
cancelRef.current = () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchResource = makeApi<{}, RESULT>({
|
||||||
|
method: 'GET',
|
||||||
|
endpoint,
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchResource({})
|
||||||
|
.then(result => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setResource({
|
||||||
|
status: ResourceStatus.COMPLETE,
|
||||||
|
result,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setResource({
|
||||||
|
status: ResourceStatus.ERROR,
|
||||||
|
result: null,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancel the request when the component un-mounts
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [endpoint]);
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For when you want to transform the result of an api resource hook before using it.
|
||||||
|
*
|
||||||
|
* @param resource the Resource object returned from useApiV1Resource
|
||||||
|
* @param transformFn a callback that transforms the result object into the shape you want.
|
||||||
|
* Make sure to use a persistent function for this so it doesn't constantly recalculate!
|
||||||
|
*/
|
||||||
|
export function useTransformedResource<IN, OUT>(
|
||||||
|
resource: Resource<IN>,
|
||||||
|
transformFn: (result: IN) => OUT,
|
||||||
|
): Resource<OUT> {
|
||||||
|
return useMemo(() => {
|
||||||
|
if (resource.status !== ResourceStatus.COMPLETE) {
|
||||||
|
// While incomplete, there is no result - no need to transform.
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...resource,
|
||||||
|
result: transformFn(resource.result),
|
||||||
|
};
|
||||||
|
}, [resource, transformFn]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the "result" field from a fetched API v1 endpoint
|
||||||
|
const extractInnerResult = <T>(responseBody: { result: T }) =>
|
||||||
|
responseBody.result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A general-purpose hook to fetch a Superset resource from a v1 API endpoint.
|
||||||
|
* Handles request lifecycle and async logic so you don't have to.
|
||||||
|
*
|
||||||
|
* This returns the data under the "result" field in the API response body.
|
||||||
|
* If you need the full response body, use {useFullApiResource} instead.
|
||||||
|
*
|
||||||
|
* @param endpoint The url where the resource is located.
|
||||||
|
*/
|
||||||
|
export function useApiV1Resource<RESULT>(endpoint: string): Resource<RESULT> {
|
||||||
|
return useTransformedResource(
|
||||||
|
useApiResourceFullBody<{ result: RESULT }>(endpoint),
|
||||||
|
extractInnerResult,
|
||||||
|
);
|
||||||
|
}
|
39
superset-frontend/src/common/hooks/apiResources/charts.ts
Normal file
39
superset-frontend/src/common/hooks/apiResources/charts.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import rison from 'rison';
|
||||||
|
import Chart from 'src/types/Chart';
|
||||||
|
import { useApiV1Resource, useTransformedResource } from './apiResources';
|
||||||
|
|
||||||
|
function extractOwnerNames({ owners }: Chart) {
|
||||||
|
if (!owners) return null;
|
||||||
|
return owners.map(owner => `${owner.first_name} ${owner.last_name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownerNamesQuery = rison.encode({
|
||||||
|
columns: ['owners.first_name', 'owners.last_name'],
|
||||||
|
keys: ['none'],
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useChartOwnerNames(chartId: string) {
|
||||||
|
return useTransformedResource(
|
||||||
|
useApiV1Resource<Chart>(`/api/v1/chart/${chartId}?q=${ownerNamesQuery}`),
|
||||||
|
extractOwnerNames,
|
||||||
|
);
|
||||||
|
}
|
29
superset-frontend/src/common/hooks/apiResources/index.ts
Normal file
29
superset-frontend/src/common/hooks/apiResources/index.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export {
|
||||||
|
useApiResourceFullBody,
|
||||||
|
useApiV1Resource,
|
||||||
|
useTransformedResource,
|
||||||
|
} from './apiResources';
|
||||||
|
|
||||||
|
// A central catalog of API Resource hooks.
|
||||||
|
// Add new API hooks here, organized under
|
||||||
|
// different files for different resource types.
|
||||||
|
export { useChartOwnerNames } from './charts';
|
@ -364,7 +364,6 @@ export default class Chart extends React.Component {
|
|||||||
timeout={timeout}
|
timeout={timeout}
|
||||||
triggerQuery={chart.triggerQuery}
|
triggerQuery={chart.triggerQuery}
|
||||||
vizType={slice.viz_type}
|
vizType={slice.viz_type}
|
||||||
owners={slice.owners}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -199,7 +199,6 @@ const ExploreChartPanel = props => {
|
|||||||
errorMessage={props.errorMessage}
|
errorMessage={props.errorMessage}
|
||||||
formData={props.form_data}
|
formData={props.form_data}
|
||||||
onQuery={props.onQuery}
|
onQuery={props.onQuery}
|
||||||
owners={props?.slice?.owners}
|
|
||||||
queriesResponse={chart.queriesResponse}
|
queriesResponse={chart.queriesResponse}
|
||||||
refreshOverlayVisible={props.refreshOverlayVisible}
|
refreshOverlayVisible={props.refreshOverlayVisible}
|
||||||
setControlValue={props.actions.setControlValue}
|
setControlValue={props.actions.setControlValue}
|
||||||
|
Loading…
Reference in New Issue
Block a user