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:
Rafael Wendel 2021-04-19 09:30:46 -03:00 committed by GitHub
parent d8d7c78992
commit 427c005c04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 65 additions and 29 deletions

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; 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 Button from "antd/lib/button";
import List from "antd/lib/list"; import List from "antd/lib/list";
import Modal from "antd/lib/modal"; import Modal from "antd/lib/modal";
@ -45,6 +45,8 @@ class CreateSourceDialog extends React.Component {
currentStep: StepEnum.SELECT_TYPE, currentStep: StepEnum.SELECT_TYPE,
}; };
formId = uniqueId("sourceForm");
selectType = selectedType => { selectType = selectedType => {
this.setState({ selectedType, currentStep: StepEnum.CONFIGURE_IT }); this.setState({ selectedType, currentStep: StepEnum.CONFIGURE_IT });
}; };
@ -117,7 +119,7 @@ class CreateSourceDialog extends React.Component {
</HelpTrigger> </HelpTrigger>
)} )}
</div> </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" && ( {selectedType.type === "databricks" && (
<small> <small>
By using the Databricks Data Source you agree to the Databricks JDBC/ODBC{" "} By using the Databricks Data Source you agree to the Databricks JDBC/ODBC{" "}
@ -171,7 +173,7 @@ class CreateSourceDialog extends React.Component {
<Button <Button
key="submit" key="submit"
htmlType="submit" htmlType="submit"
form="sourceForm" form={this.formId}
type="primary" type="primary"
loading={savingSource} loading={savingSource}
data-test="CreateSourceSaveButton"> data-test="CreateSourceSaveButton">

View File

@ -11,6 +11,7 @@ import Divider from "antd/lib/divider";
import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper"; import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper";
import QuerySelector from "@/components/QuerySelector"; import QuerySelector from "@/components/QuerySelector";
import { Query } from "@/services/query"; import { Query } from "@/services/query";
import { useUniqueId } from "@/lib/hooks/useUniqueId";
const { Option } = Select; const { Option } = Select;
const formItemProps = { labelCol: { span: 6 }, wrapperCol: { span: 16 } }; const formItemProps = { labelCol: { span: 6 }, wrapperCol: { span: 16 } };
@ -111,6 +112,8 @@ function EditParameterSettingsDialog(props) {
props.dialog.close(param); props.dialog.close(param);
} }
const paramFormId = useUniqueId("paramForm");
return ( return (
<Modal <Modal
{...props.dialog.props} {...props.dialog.props}
@ -125,12 +128,12 @@ function EditParameterSettingsDialog(props) {
htmlType="submit" htmlType="submit"
disabled={!isFulfilled()} disabled={!isFulfilled()}
type="primary" type="primary"
form="paramForm" form={paramFormId}
data-test="SaveParameterSettings"> data-test="SaveParameterSettings">
{isNew ? "Add Parameter" : "OK"} {isNew ? "Add Parameter" : "OK"}
</Button>, </Button>,
]}> ]}>
<Form layout="horizontal" onFinish={onConfirm} id="paramForm"> <Form layout="horizontal" onFinish={onConfirm} id={paramFormId}>
{isNew && ( {isNew && (
<NameInput <NameInput
name={param.name} name={param.name}

View File

@ -8,12 +8,15 @@ import { MappingType, ParameterMappingListInput } from "@/components/ParameterMa
import QuerySelector from "@/components/QuerySelector"; import QuerySelector from "@/components/QuerySelector";
import notification from "@/services/notification"; import notification from "@/services/notification";
import { Query } from "@/services/query"; import { Query } from "@/services/query";
import { useUniqueId } from "@/lib/hooks/useUniqueId";
function VisualizationSelect({ query, visualization, onChange }) { function VisualizationSelect({ query, visualization, onChange }) {
const visualizationGroups = useMemo(() => { const visualizationGroups = useMemo(() => {
return query ? groupBy(query.visualizations, "type") : {}; return query ? groupBy(query.visualizations, "type") : {};
}, [query]); }, [query]);
const vizSelectId = useUniqueId("visualization-select");
const handleChange = useCallback( const handleChange = useCallback(
visualizationId => { visualizationId => {
const selectedVisualization = query ? find(query.visualizations, { id: visualizationId }) : null; const selectedVisualization = query ? find(query.visualizations, { id: visualizationId }) : null;
@ -29,9 +32,9 @@ function VisualizationSelect({ query, visualization, onChange }) {
return ( return (
<div> <div>
<div className="form-group"> <div className="form-group">
<label htmlFor="choose-visualization">Choose Visualization</label> <label htmlFor={vizSelectId}>Choose Visualization</label>
<Select <Select
id="choose-visualization" id={vizSelectId}
className="w-100" className="w-100"
value={visualization ? visualization.id : undefined} value={visualization ? visualization.id : undefined}
onChange={handleChange}> onChange={handleChange}>
@ -108,6 +111,7 @@ function AddWidgetDialog({ dialog, dashboard }) {
}, [dialog, selectedVisualization, parameterMappings]); }, [dialog, selectedVisualization, parameterMappings]);
const existingParams = dashboard.getParametersDefs(); const existingParams = dashboard.getParametersDefs();
const parameterMappingsId = useUniqueId("parameter-mappings");
return ( return (
<Modal <Modal
@ -132,12 +136,12 @@ function AddWidgetDialog({ dialog, dashboard }) {
)} )}
{parameterMappings.length > 0 && [ {parameterMappings.length > 0 && [
<label key="parameters-title" htmlFor="parameter-mappings"> <label key="parameters-title" htmlFor={parameterMappingsId}>
Parameters Parameters
</label>, </label>,
<ParameterMappingListInput <ParameterMappingListInput
key="parameters-list" key="parameters-list"
id="parameter-mappings" id={parameterMappingsId}
mappings={parameterMappings} mappings={parameterMappings}
existingParams={existingParams} existingParams={existingParams}
onChange={setParameterMappings} onChange={setParameterMappings}

View File

@ -9,6 +9,7 @@ import CodeBlock from "@/components/CodeBlock";
import { axios } from "@/services/axios"; import { axios } from "@/services/axios";
import { clientConfig } from "@/services/auth"; import { clientConfig } from "@/services/auth";
import notification from "@/services/notification"; import notification from "@/services/notification";
import { useUniqueId } from "@/lib/hooks/useUniqueId";
import "./index.less"; import "./index.less";
import { policy } from "@/services/policy"; import { policy } from "@/services/policy";
@ -39,6 +40,9 @@ function ApiKeyDialog({ dialog, ...props }) {
[query.id, query.api_key] [query.id, query.api_key]
); );
const csvResultsLabelId = useUniqueId("csv-results-label");
const jsonResultsLabelId = useUniqueId("json-results-label");
return ( return (
<Modal {...dialog.props} width={600} footer={<Button onClick={() => dialog.close(query)}>Close</Button>}> <Modal {...dialog.props} width={600} footer={<Button onClick={() => dialog.close(query)}>Close</Button>}>
<div className="query-api-key-dialog-wrapper"> <div className="query-api-key-dialog-wrapper">
@ -56,14 +60,14 @@ function ApiKeyDialog({ dialog, ...props }) {
<h5>Example API Calls:</h5> <h5>Example API Calls:</h5>
<div className="m-b-10"> <div className="m-b-10">
<span id="csv-results-label">Results in CSV format:</span> <span id={csvResultsLabelId}>Results in CSV format:</span>
<CodeBlock aria-labelledby="csv-results-label" copyable> <CodeBlock aria-labelledby={csvResultsLabelId} copyable>
{csvUrl} {csvUrl}
</CodeBlock> </CodeBlock>
</div> </div>
<div> <div>
<span id="json-results-label">Results in JSON format:</span> <span id={jsonResultsLabelId}>Results in JSON format:</span>
<CodeBlock aria-labelledby="json-results-label" copyable> <CodeBlock aria-labelledby={jsonResultsLabelId} copyable>
{jsonUrl} {jsonUrl}
</CodeBlock> </CodeBlock>
</div> </div>

View File

@ -1,3 +1,4 @@
import { uniqueId } from "lodash";
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Alert from "antd/lib/alert"; 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 { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper";
import { clientConfig } from "@/services/auth"; import { clientConfig } from "@/services/auth";
import CodeBlock from "@/components/CodeBlock"; import CodeBlock from "@/components/CodeBlock";
import "./EmbedQueryDialog.less"; import "./EmbedQueryDialog.less";
class EmbedQueryDialog extends React.Component { 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() { render() {
const { query, dialog } = this.props; const { query, dialog } = this.props;
const { enableChangeIframeSize, iframeWidth, iframeHeight } = this.state; const { enableChangeIframeSize, iframeWidth, iframeHeight } = this.state;
@ -48,19 +53,19 @@ class EmbedQueryDialog extends React.Component {
footer={<Button onClick={dialog.dismiss}>Close</Button>}> footer={<Button onClick={dialog.dismiss}>Close</Button>}>
{query.is_safe ? ( {query.is_safe ? (
<React.Fragment> <React.Fragment>
<h5 id="url-embed-label" className="m-t-0"> <h5 id={this.urlEmbedLabelId} className="m-t-0">
Public URL Public URL
</h5> </h5>
<div className="m-b-30"> <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} {this.embedUrl}
</CodeBlock> </CodeBlock>
</div> </div>
<h5 id="iframe-embed-label" className="m-t-0"> <h5 id={this.iframeEmbedLabelId} className="m-t-0">
IFrame Embed IFrame Embed
</h5> </h5>
<div> <div>
<CodeBlock aria-labelledby="iframe-embed-label" copyable> <CodeBlock aria-labelledby={this.iframeEmbedLabelId} copyable>
{`<iframe src="${this.embedUrl}" width="${iframeWidth}" height="${iframeHeight}"></iframe>`} {`<iframe src="${this.embedUrl}" width="${iframeWidth}" height="${iframeHeight}"></iframe>`}
</CodeBlock> </CodeBlock>
<Form className="m-t-10" layout="inline"> <Form className="m-t-10" layout="inline">

View File

@ -5,6 +5,7 @@ import Button from "antd/lib/button";
import Modal from "antd/lib/modal"; import Modal from "antd/lib/modal";
import DynamicForm from "@/components/dynamic-form/DynamicForm"; import DynamicForm from "@/components/dynamic-form/DynamicForm";
import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper"; import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper";
import { useUniqueId } from "@/lib/hooks/useUniqueId";
function QuerySnippetDialog({ querySnippet, dialog, readOnly }) { function QuerySnippetDialog({ querySnippet, dialog, readOnly }) {
const handleSubmit = useCallback( const handleSubmit = useCallback(
@ -31,6 +32,8 @@ function QuerySnippetDialog({ querySnippet, dialog, readOnly }) {
{ name: "snippet", title: "Snippet", type: "ace", required: true }, { name: "snippet", title: "Snippet", type: "ace", required: true },
].map(field => ({ ...field, readOnly, initialValue: get(querySnippet, field.name, "") })); ].map(field => ({ ...field, readOnly, initialValue: get(querySnippet, field.name, "") }));
const querySnippetsFormId = useUniqueId("querySnippetForm");
return ( return (
<Modal <Modal
{...dialog.props} {...dialog.props}
@ -46,7 +49,7 @@ function QuerySnippetDialog({ querySnippet, dialog, readOnly }) {
disabled={readOnly || dialog.props.okButtonProps.disabled} disabled={readOnly || dialog.props.okButtonProps.disabled}
htmlType="submit" htmlType="submit"
type="primary" type="primary"
form="querySnippetForm" form={querySnippetsFormId}
data-test="SaveQuerySnippetButton"> data-test="SaveQuerySnippetButton">
{isEditing ? "Save" : "Create"} {isEditing ? "Save" : "Create"}
</Button> </Button>
@ -55,7 +58,13 @@ function QuerySnippetDialog({ querySnippet, dialog, readOnly }) {
wrapProps={{ wrapProps={{
"data-test": "QuerySnippetDialog", "data-test": "QuerySnippetDialog",
}}> }}>
<DynamicForm id="querySnippetForm" fields={formFields} onSubmit={handleSubmit} hideSubmitButton feedbackIcons /> <DynamicForm
id={querySnippetsFormId}
fields={formFields}
onSubmit={handleSubmit}
hideSubmitButton
feedbackIcons
/>
</Modal> </Modal>
); );
} }

View File

@ -10,6 +10,7 @@ import notification from "@/services/notification";
import Visualization from "@/services/visualization"; import Visualization from "@/services/visualization";
import recordEvent from "@/services/recordEvent"; import recordEvent from "@/services/recordEvent";
import useQueryResultData from "@/lib/useQueryResultData"; import useQueryResultData from "@/lib/useQueryResultData";
import { useUniqueId } from "@/lib/hooks/useUniqueId";
import { import {
registeredVisualizations, registeredVisualizations,
getDefaultVisualization, getDefaultVisualization,
@ -156,6 +157,9 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
? filter(sortBy(registeredVisualizations, ["name"]), vis => !vis.isDeprecated) ? filter(sortBy(registeredVisualizations, ["name"]), vis => !vis.isDeprecated)
: pick(registeredVisualizations, [type]); : pick(registeredVisualizations, [type]);
const vizTypeId = useUniqueId("visualization-type");
const vizNameId = useUniqueId("visualization-name");
return ( return (
<Modal <Modal
{...dialog.props} {...dialog.props}
@ -172,10 +176,10 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
<div className="edit-visualization-dialog"> <div className="edit-visualization-dialog">
<div className="visualization-settings"> <div className="visualization-settings">
<div className="m-b-15"> <div className="m-b-15">
<label htmlFor="visualization-type">Visualization Type</label> <label htmlFor={vizTypeId}>Visualization Type</label>
<Select <Select
data-test="VisualizationType" data-test="VisualizationType"
id="visualization-type" id={vizTypeId}
className="w-100" className="w-100"
disabled={!isNew} disabled={!isNew}
value={type} value={type}
@ -188,10 +192,10 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
</Select> </Select>
</div> </div>
<div className="m-b-15"> <div className="m-b-15">
<label htmlFor="visualization-name">Visualization Name</label> <label htmlFor={vizNameId}>Visualization Name</label>
<Input <Input
data-test="VisualizationName" data-test="VisualizationName"
id="visualization-name" id={vizNameId}
className="w-100" className="w-100"
value={name} value={name}
onChange={event => onNameChanged(event.target.value)} onChange={event => onNameChanged(event.target.value)}

View File

@ -1,4 +1,4 @@
import { map } from "lodash"; import { map, uniqueId } from "lodash";
import React from "react"; import React from "react";
import Switch from "antd/lib/switch"; import Switch from "antd/lib/switch";
@ -70,6 +70,7 @@ class OutdatedQueries extends React.Component {
}; };
_updateTimer = null; _updateTimer = null;
autoUpdateSwitchId = uniqueId("auto-update-switch");
componentDidMount() { componentDidMount() {
recordEvent("view", "page", "admin/queries/outdated"); recordEvent("view", "page", "admin/queries/outdated");
@ -93,11 +94,11 @@ class OutdatedQueries extends React.Component {
<Layout activeTab={controller.params.currentPage}> <Layout activeTab={controller.params.currentPage}>
<div className="m-15"> <div className="m-15">
<div> <div>
<label htmlFor="auto-update-switch" className="m-0"> <label htmlFor={this.autoUpdateSwitchId} className="m-0">
Auto update Auto update
</label> </label>
<Switch <Switch
id="auto-update-switch" id={this.autoUpdateSwitchId}
className="m-l-10" className="m-l-10"
checked={this.state.autoUpdate} checked={this.state.autoUpdate}
onChange={autoUpdate => this.setState({ autoUpdate })} onChange={autoUpdate => this.setState({ autoUpdate })}

View File

@ -8,12 +8,14 @@ import InputWithCopy from "@/components/InputWithCopy";
import { UserProfile } from "@/components/proptypes"; import { UserProfile } from "@/components/proptypes";
import User from "@/services/user"; import User from "@/services/user";
import useImmutableCallback from "@/lib/hooks/useImmutableCallback"; import useImmutableCallback from "@/lib/hooks/useImmutableCallback";
import { useUniqueId } from "@/lib/hooks/useUniqueId";
export default function ApiKeyForm(props) { export default function ApiKeyForm(props) {
const { user, onChange } = props; const { user, onChange } = props;
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleChange = useImmutableCallback(onChange); const handleChange = useImmutableCallback(onChange);
const apiKeyInputId = useUniqueId("apiKey");
const regenerateApiKey = useCallback(() => { const regenerateApiKey = useCallback(() => {
const doRegenerate = () => { const doRegenerate = () => {
@ -44,7 +46,7 @@ export default function ApiKeyForm(props) {
<Form layout="vertical"> <Form layout="vertical">
<hr /> <hr />
<Form.Item label="API Key" className="m-b-10"> <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> </Form.Item>
<Button className="w-100" onClick={regenerateApiKey} loading={loading} data-test="RegenerateApiKey"> <Button className="w-100" onClick={regenerateApiKey} loading={loading} data-test="RegenerateApiKey">
Regenerate Regenerate

View File

@ -5,6 +5,7 @@ import Alert from "antd/lib/alert";
import DynamicForm from "@/components/dynamic-form/DynamicForm"; import DynamicForm from "@/components/dynamic-form/DynamicForm";
import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper"; import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper";
import recordEvent from "@/services/recordEvent"; import recordEvent from "@/services/recordEvent";
import { useUniqueId } from "@/lib/hooks/useUniqueId";
const formFields = [ const formFields = [
{ required: true, name: "name", title: "Name", type: "text", autoFocus: true }, { 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 handleSubmit = useCallback(values => dialog.close(values).catch(setError), [dialog]);
const formId = useUniqueId("userForm");
return ( return (
<Modal <Modal
@ -32,7 +34,7 @@ function CreateUserDialog({ dialog }) {
{...dialog.props.okButtonProps} {...dialog.props.okButtonProps}
htmlType="submit" htmlType="submit"
type="primary" type="primary"
form="userForm" form={formId}
data-test="SaveUserButton"> data-test="SaveUserButton">
Create Create
</Button>, </Button>,
@ -40,7 +42,7 @@ function CreateUserDialog({ dialog }) {
wrapProps={{ wrapProps={{
"data-test": "CreateUserDialog", "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" />} {error && <Alert message={error.message} type="error" showIcon data-test="CreateUserErrorAlert" />}
</Modal> </Modal>
); );