mirror of
https://github.com/valitydev/redash.git
synced 2024-11-06 17:15:17 +00:00
Replace hardcoded ids with hook (#5444)
* refactor: replace hardcoded ids with hook * refactor: replace hard coded ids with lodash id (class)
This commit is contained in:
parent
d8d7c78992
commit
427c005c04
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { isEmpty, toUpper, includes, get } from "lodash";
|
||||
import { isEmpty, toUpper, includes, get, uniqueId } from "lodash";
|
||||
import Button from "antd/lib/button";
|
||||
import List from "antd/lib/list";
|
||||
import Modal from "antd/lib/modal";
|
||||
@ -45,6 +45,8 @@ class CreateSourceDialog extends React.Component {
|
||||
currentStep: StepEnum.SELECT_TYPE,
|
||||
};
|
||||
|
||||
formId = uniqueId("sourceForm");
|
||||
|
||||
selectType = selectedType => {
|
||||
this.setState({ selectedType, currentStep: StepEnum.CONFIGURE_IT });
|
||||
};
|
||||
@ -117,7 +119,7 @@ class CreateSourceDialog extends React.Component {
|
||||
</HelpTrigger>
|
||||
)}
|
||||
</div>
|
||||
<DynamicForm id="sourceForm" fields={fields} onSubmit={this.createSource} feedbackIcons hideSubmitButton />
|
||||
<DynamicForm id={this.formId} fields={fields} onSubmit={this.createSource} feedbackIcons hideSubmitButton />
|
||||
{selectedType.type === "databricks" && (
|
||||
<small>
|
||||
By using the Databricks Data Source you agree to the Databricks JDBC/ODBC{" "}
|
||||
@ -171,7 +173,7 @@ class CreateSourceDialog extends React.Component {
|
||||
<Button
|
||||
key="submit"
|
||||
htmlType="submit"
|
||||
form="sourceForm"
|
||||
form={this.formId}
|
||||
type="primary"
|
||||
loading={savingSource}
|
||||
data-test="CreateSourceSaveButton">
|
||||
|
@ -11,6 +11,7 @@ import Divider from "antd/lib/divider";
|
||||
import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper";
|
||||
import QuerySelector from "@/components/QuerySelector";
|
||||
import { Query } from "@/services/query";
|
||||
import { useUniqueId } from "@/lib/hooks/useUniqueId";
|
||||
|
||||
const { Option } = Select;
|
||||
const formItemProps = { labelCol: { span: 6 }, wrapperCol: { span: 16 } };
|
||||
@ -111,6 +112,8 @@ function EditParameterSettingsDialog(props) {
|
||||
props.dialog.close(param);
|
||||
}
|
||||
|
||||
const paramFormId = useUniqueId("paramForm");
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...props.dialog.props}
|
||||
@ -125,12 +128,12 @@ function EditParameterSettingsDialog(props) {
|
||||
htmlType="submit"
|
||||
disabled={!isFulfilled()}
|
||||
type="primary"
|
||||
form="paramForm"
|
||||
form={paramFormId}
|
||||
data-test="SaveParameterSettings">
|
||||
{isNew ? "Add Parameter" : "OK"}
|
||||
</Button>,
|
||||
]}>
|
||||
<Form layout="horizontal" onFinish={onConfirm} id="paramForm">
|
||||
<Form layout="horizontal" onFinish={onConfirm} id={paramFormId}>
|
||||
{isNew && (
|
||||
<NameInput
|
||||
name={param.name}
|
||||
|
@ -8,12 +8,15 @@ import { MappingType, ParameterMappingListInput } from "@/components/ParameterMa
|
||||
import QuerySelector from "@/components/QuerySelector";
|
||||
import notification from "@/services/notification";
|
||||
import { Query } from "@/services/query";
|
||||
import { useUniqueId } from "@/lib/hooks/useUniqueId";
|
||||
|
||||
function VisualizationSelect({ query, visualization, onChange }) {
|
||||
const visualizationGroups = useMemo(() => {
|
||||
return query ? groupBy(query.visualizations, "type") : {};
|
||||
}, [query]);
|
||||
|
||||
const vizSelectId = useUniqueId("visualization-select");
|
||||
|
||||
const handleChange = useCallback(
|
||||
visualizationId => {
|
||||
const selectedVisualization = query ? find(query.visualizations, { id: visualizationId }) : null;
|
||||
@ -29,9 +32,9 @@ function VisualizationSelect({ query, visualization, onChange }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="choose-visualization">Choose Visualization</label>
|
||||
<label htmlFor={vizSelectId}>Choose Visualization</label>
|
||||
<Select
|
||||
id="choose-visualization"
|
||||
id={vizSelectId}
|
||||
className="w-100"
|
||||
value={visualization ? visualization.id : undefined}
|
||||
onChange={handleChange}>
|
||||
@ -108,6 +111,7 @@ function AddWidgetDialog({ dialog, dashboard }) {
|
||||
}, [dialog, selectedVisualization, parameterMappings]);
|
||||
|
||||
const existingParams = dashboard.getParametersDefs();
|
||||
const parameterMappingsId = useUniqueId("parameter-mappings");
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -132,12 +136,12 @@ function AddWidgetDialog({ dialog, dashboard }) {
|
||||
)}
|
||||
|
||||
{parameterMappings.length > 0 && [
|
||||
<label key="parameters-title" htmlFor="parameter-mappings">
|
||||
<label key="parameters-title" htmlFor={parameterMappingsId}>
|
||||
Parameters
|
||||
</label>,
|
||||
<ParameterMappingListInput
|
||||
key="parameters-list"
|
||||
id="parameter-mappings"
|
||||
id={parameterMappingsId}
|
||||
mappings={parameterMappings}
|
||||
existingParams={existingParams}
|
||||
onChange={setParameterMappings}
|
||||
|
@ -9,6 +9,7 @@ import CodeBlock from "@/components/CodeBlock";
|
||||
import { axios } from "@/services/axios";
|
||||
import { clientConfig } from "@/services/auth";
|
||||
import notification from "@/services/notification";
|
||||
import { useUniqueId } from "@/lib/hooks/useUniqueId";
|
||||
|
||||
import "./index.less";
|
||||
import { policy } from "@/services/policy";
|
||||
@ -39,6 +40,9 @@ function ApiKeyDialog({ dialog, ...props }) {
|
||||
[query.id, query.api_key]
|
||||
);
|
||||
|
||||
const csvResultsLabelId = useUniqueId("csv-results-label");
|
||||
const jsonResultsLabelId = useUniqueId("json-results-label");
|
||||
|
||||
return (
|
||||
<Modal {...dialog.props} width={600} footer={<Button onClick={() => dialog.close(query)}>Close</Button>}>
|
||||
<div className="query-api-key-dialog-wrapper">
|
||||
@ -56,14 +60,14 @@ function ApiKeyDialog({ dialog, ...props }) {
|
||||
|
||||
<h5>Example API Calls:</h5>
|
||||
<div className="m-b-10">
|
||||
<span id="csv-results-label">Results in CSV format:</span>
|
||||
<CodeBlock aria-labelledby="csv-results-label" copyable>
|
||||
<span id={csvResultsLabelId}>Results in CSV format:</span>
|
||||
<CodeBlock aria-labelledby={csvResultsLabelId} copyable>
|
||||
{csvUrl}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
<div>
|
||||
<span id="json-results-label">Results in JSON format:</span>
|
||||
<CodeBlock aria-labelledby="json-results-label" copyable>
|
||||
<span id={jsonResultsLabelId}>Results in JSON format:</span>
|
||||
<CodeBlock aria-labelledby={jsonResultsLabelId} copyable>
|
||||
{jsonUrl}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { uniqueId } from "lodash";
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Alert from "antd/lib/alert";
|
||||
@ -9,6 +10,7 @@ import Modal from "antd/lib/modal";
|
||||
import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper";
|
||||
import { clientConfig } from "@/services/auth";
|
||||
import CodeBlock from "@/components/CodeBlock";
|
||||
|
||||
import "./EmbedQueryDialog.less";
|
||||
|
||||
class EmbedQueryDialog extends React.Component {
|
||||
@ -36,6 +38,9 @@ class EmbedQueryDialog extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
urlEmbedLabelId = uniqueId("url-embed-label");
|
||||
iframeEmbedLabelId = uniqueId("iframe-embed-label");
|
||||
|
||||
render() {
|
||||
const { query, dialog } = this.props;
|
||||
const { enableChangeIframeSize, iframeWidth, iframeHeight } = this.state;
|
||||
@ -48,19 +53,19 @@ class EmbedQueryDialog extends React.Component {
|
||||
footer={<Button onClick={dialog.dismiss}>Close</Button>}>
|
||||
{query.is_safe ? (
|
||||
<React.Fragment>
|
||||
<h5 id="url-embed-label" className="m-t-0">
|
||||
<h5 id={this.urlEmbedLabelId} className="m-t-0">
|
||||
Public URL
|
||||
</h5>
|
||||
<div className="m-b-30">
|
||||
<CodeBlock aria-labelledby="url-embed-label" data-test="EmbedIframe" copyable>
|
||||
<CodeBlock aria-labelledby={this.urlEmbedLabelId} data-test="EmbedIframe" copyable>
|
||||
{this.embedUrl}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
<h5 id="iframe-embed-label" className="m-t-0">
|
||||
<h5 id={this.iframeEmbedLabelId} className="m-t-0">
|
||||
IFrame Embed
|
||||
</h5>
|
||||
<div>
|
||||
<CodeBlock aria-labelledby="iframe-embed-label" copyable>
|
||||
<CodeBlock aria-labelledby={this.iframeEmbedLabelId} copyable>
|
||||
{`<iframe src="${this.embedUrl}" width="${iframeWidth}" height="${iframeHeight}"></iframe>`}
|
||||
</CodeBlock>
|
||||
<Form className="m-t-10" layout="inline">
|
||||
|
@ -5,6 +5,7 @@ import Button from "antd/lib/button";
|
||||
import Modal from "antd/lib/modal";
|
||||
import DynamicForm from "@/components/dynamic-form/DynamicForm";
|
||||
import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper";
|
||||
import { useUniqueId } from "@/lib/hooks/useUniqueId";
|
||||
|
||||
function QuerySnippetDialog({ querySnippet, dialog, readOnly }) {
|
||||
const handleSubmit = useCallback(
|
||||
@ -31,6 +32,8 @@ function QuerySnippetDialog({ querySnippet, dialog, readOnly }) {
|
||||
{ name: "snippet", title: "Snippet", type: "ace", required: true },
|
||||
].map(field => ({ ...field, readOnly, initialValue: get(querySnippet, field.name, "") }));
|
||||
|
||||
const querySnippetsFormId = useUniqueId("querySnippetForm");
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...dialog.props}
|
||||
@ -46,7 +49,7 @@ function QuerySnippetDialog({ querySnippet, dialog, readOnly }) {
|
||||
disabled={readOnly || dialog.props.okButtonProps.disabled}
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
form="querySnippetForm"
|
||||
form={querySnippetsFormId}
|
||||
data-test="SaveQuerySnippetButton">
|
||||
{isEditing ? "Save" : "Create"}
|
||||
</Button>
|
||||
@ -55,7 +58,13 @@ function QuerySnippetDialog({ querySnippet, dialog, readOnly }) {
|
||||
wrapProps={{
|
||||
"data-test": "QuerySnippetDialog",
|
||||
}}>
|
||||
<DynamicForm id="querySnippetForm" fields={formFields} onSubmit={handleSubmit} hideSubmitButton feedbackIcons />
|
||||
<DynamicForm
|
||||
id={querySnippetsFormId}
|
||||
fields={formFields}
|
||||
onSubmit={handleSubmit}
|
||||
hideSubmitButton
|
||||
feedbackIcons
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import notification from "@/services/notification";
|
||||
import Visualization from "@/services/visualization";
|
||||
import recordEvent from "@/services/recordEvent";
|
||||
import useQueryResultData from "@/lib/useQueryResultData";
|
||||
import { useUniqueId } from "@/lib/hooks/useUniqueId";
|
||||
import {
|
||||
registeredVisualizations,
|
||||
getDefaultVisualization,
|
||||
@ -156,6 +157,9 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
|
||||
? filter(sortBy(registeredVisualizations, ["name"]), vis => !vis.isDeprecated)
|
||||
: pick(registeredVisualizations, [type]);
|
||||
|
||||
const vizTypeId = useUniqueId("visualization-type");
|
||||
const vizNameId = useUniqueId("visualization-name");
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...dialog.props}
|
||||
@ -172,10 +176,10 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
|
||||
<div className="edit-visualization-dialog">
|
||||
<div className="visualization-settings">
|
||||
<div className="m-b-15">
|
||||
<label htmlFor="visualization-type">Visualization Type</label>
|
||||
<label htmlFor={vizTypeId}>Visualization Type</label>
|
||||
<Select
|
||||
data-test="VisualizationType"
|
||||
id="visualization-type"
|
||||
id={vizTypeId}
|
||||
className="w-100"
|
||||
disabled={!isNew}
|
||||
value={type}
|
||||
@ -188,10 +192,10 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
|
||||
</Select>
|
||||
</div>
|
||||
<div className="m-b-15">
|
||||
<label htmlFor="visualization-name">Visualization Name</label>
|
||||
<label htmlFor={vizNameId}>Visualization Name</label>
|
||||
<Input
|
||||
data-test="VisualizationName"
|
||||
id="visualization-name"
|
||||
id={vizNameId}
|
||||
className="w-100"
|
||||
value={name}
|
||||
onChange={event => onNameChanged(event.target.value)}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { map } from "lodash";
|
||||
import { map, uniqueId } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
import Switch from "antd/lib/switch";
|
||||
@ -70,6 +70,7 @@ class OutdatedQueries extends React.Component {
|
||||
};
|
||||
|
||||
_updateTimer = null;
|
||||
autoUpdateSwitchId = uniqueId("auto-update-switch");
|
||||
|
||||
componentDidMount() {
|
||||
recordEvent("view", "page", "admin/queries/outdated");
|
||||
@ -93,11 +94,11 @@ class OutdatedQueries extends React.Component {
|
||||
<Layout activeTab={controller.params.currentPage}>
|
||||
<div className="m-15">
|
||||
<div>
|
||||
<label htmlFor="auto-update-switch" className="m-0">
|
||||
<label htmlFor={this.autoUpdateSwitchId} className="m-0">
|
||||
Auto update
|
||||
</label>
|
||||
<Switch
|
||||
id="auto-update-switch"
|
||||
id={this.autoUpdateSwitchId}
|
||||
className="m-l-10"
|
||||
checked={this.state.autoUpdate}
|
||||
onChange={autoUpdate => this.setState({ autoUpdate })}
|
||||
|
@ -8,12 +8,14 @@ import InputWithCopy from "@/components/InputWithCopy";
|
||||
import { UserProfile } from "@/components/proptypes";
|
||||
import User from "@/services/user";
|
||||
import useImmutableCallback from "@/lib/hooks/useImmutableCallback";
|
||||
import { useUniqueId } from "@/lib/hooks/useUniqueId";
|
||||
|
||||
export default function ApiKeyForm(props) {
|
||||
const { user, onChange } = props;
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleChange = useImmutableCallback(onChange);
|
||||
const apiKeyInputId = useUniqueId("apiKey");
|
||||
|
||||
const regenerateApiKey = useCallback(() => {
|
||||
const doRegenerate = () => {
|
||||
@ -44,7 +46,7 @@ export default function ApiKeyForm(props) {
|
||||
<Form layout="vertical">
|
||||
<hr />
|
||||
<Form.Item label="API Key" className="m-b-10">
|
||||
<InputWithCopy id="apiKey" className="hide-in-percy" value={user.apiKey} data-test="ApiKey" readOnly />
|
||||
<InputWithCopy id={apiKeyInputId} className="hide-in-percy" value={user.apiKey} data-test="ApiKey" readOnly />
|
||||
</Form.Item>
|
||||
<Button className="w-100" onClick={regenerateApiKey} loading={loading} data-test="RegenerateApiKey">
|
||||
Regenerate
|
||||
|
@ -5,6 +5,7 @@ import Alert from "antd/lib/alert";
|
||||
import DynamicForm from "@/components/dynamic-form/DynamicForm";
|
||||
import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper";
|
||||
import recordEvent from "@/services/recordEvent";
|
||||
import { useUniqueId } from "@/lib/hooks/useUniqueId";
|
||||
|
||||
const formFields = [
|
||||
{ required: true, name: "name", title: "Name", type: "text", autoFocus: true },
|
||||
@ -18,6 +19,7 @@ function CreateUserDialog({ dialog }) {
|
||||
}, []);
|
||||
|
||||
const handleSubmit = useCallback(values => dialog.close(values).catch(setError), [dialog]);
|
||||
const formId = useUniqueId("userForm");
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -32,7 +34,7 @@ function CreateUserDialog({ dialog }) {
|
||||
{...dialog.props.okButtonProps}
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
form="userForm"
|
||||
form={formId}
|
||||
data-test="SaveUserButton">
|
||||
Create
|
||||
</Button>,
|
||||
@ -40,7 +42,7 @@ function CreateUserDialog({ dialog }) {
|
||||
wrapProps={{
|
||||
"data-test": "CreateUserDialog",
|
||||
}}>
|
||||
<DynamicForm id="userForm" fields={formFields} onSubmit={handleSubmit} hideSubmitButton />
|
||||
<DynamicForm id={formId} fields={formFields} onSubmit={handleSubmit} hideSubmitButton />
|
||||
{error && <Alert message={error.message} type="error" showIcon data-test="CreateUserErrorAlert" />}
|
||||
</Modal>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user