client/app/pages/queries/components/QueryPageHeader.jsx (220 lines of code) (raw):

import { extend, map, filter, reduce } from "lodash"; import React, { useMemo } from "react"; import PropTypes from "prop-types"; import Button from "antd/lib/button"; import Dropdown from "antd/lib/dropdown"; import Menu from "antd/lib/menu"; import Icon from "antd/lib/icon"; import useMedia from "use-media"; import EditInPlace from "@/components/EditInPlace"; import FavoritesControl from "@/components/FavoritesControl"; import { QueryTagsControl } from "@/components/tags-control/TagsControl"; import getTags from "@/services/getTags"; import { clientConfig } from "@/services/auth"; import useQueryFlags from "../hooks/useQueryFlags"; import useArchiveQuery from "../hooks/useArchiveQuery"; import usePublishQuery from "../hooks/usePublishQuery"; import useUnpublishQuery from "../hooks/useUnpublishQuery"; import useUpdateQueryTags from "../hooks/useUpdateQueryTags"; import useRenameQuery from "../hooks/useRenameQuery"; import useDuplicateQuery from "../hooks/useDuplicateQuery"; import useApiKeyDialog from "../hooks/useApiKeyDialog"; import usePermissionsEditorDialog from "../hooks/usePermissionsEditorDialog"; import "./QueryPageHeader.less"; function getQueryTags() { return getTags("api/queries/tags").then(tags => map(tags, t => t.name)); } function createMenu(menu) { const handlers = {}; const groups = map(menu, group => filter( map(group, (props, key) => { props = extend({ isAvailable: true, isEnabled: true, onClick: () => {} }, props); if (props.isAvailable) { handlers[key] = props.onClick; return ( <Menu.Item key={key} disabled={!props.isEnabled}> {props.title} </Menu.Item> ); } return null; }) ) ); return ( <Menu onClick={({ key }) => handlers[key]()}> {reduce( filter(groups, group => group.length > 0), (result, items, key) => { const divider = result.length > 0 ? <Menu.Divider key={`divider${key}`} /> : null; return [...result, divider, ...items]; }, [] )} </Menu> ); } export default function QueryPageHeader({ query, dataSource, sourceMode, selectedVisualization, headerExtra, tagsExtra, onChange, }) { const isDesktop = useMedia({ minWidth: 768 }); const queryFlags = useQueryFlags(query, dataSource); const updateName = useRenameQuery(query, onChange); const updateTags = useUpdateQueryTags(query, onChange); const archiveQuery = useArchiveQuery(query, onChange); const publishQuery = usePublishQuery(query, onChange); const unpublishQuery = useUnpublishQuery(query, onChange); const [isDuplicating, duplicateQuery] = useDuplicateQuery(query); const openApiKeyDialog = useApiKeyDialog(query, onChange); const openPermissionsEditorDialog = usePermissionsEditorDialog(query); const moreActionsMenu = useMemo( () => createMenu([ { fork: { isEnabled: !queryFlags.isNew && queryFlags.canFork && !isDuplicating, title: ( <React.Fragment> Fork <i className="fa fa-external-link m-l-5" /> </React.Fragment> ), onClick: duplicateQuery, }, }, { archive: { isAvailable: !queryFlags.isNew && queryFlags.canEdit && !queryFlags.isArchived, title: "Archive", onClick: archiveQuery, }, managePermissions: { isAvailable: !queryFlags.isNew && queryFlags.canEdit && !queryFlags.isArchived && clientConfig.showPermissionsControl, title: "Manage Permissions", onClick: openPermissionsEditorDialog, }, publish: { isAvailable: !isDesktop && queryFlags.isDraft && !queryFlags.isArchived && !queryFlags.isNew && queryFlags.canEdit, title: "Publish", onClick: publishQuery, }, unpublish: { isAvailable: !queryFlags.isNew && queryFlags.canEdit && !queryFlags.isDraft, title: "Unpublish", onClick: unpublishQuery, }, }, { showAPIKey: { isAvailable: !queryFlags.isNew, title: "Show API Key", onClick: openApiKeyDialog, }, }, ]), [ queryFlags.isNew, queryFlags.canFork, queryFlags.canEdit, queryFlags.isArchived, queryFlags.isDraft, isDuplicating, duplicateQuery, archiveQuery, openPermissionsEditorDialog, isDesktop, publishQuery, unpublishQuery, openApiKeyDialog, ] ); return ( <div className="query-page-header"> <div className="title-with-tags"> <div className="page-title"> <div className="d-flex align-items-center"> {!queryFlags.isNew && <FavoritesControl item={query} />} <h3> <EditInPlace isEditable={queryFlags.canEdit} onDone={updateName} ignoreBlanks value={query.name} /> </h3> </div> </div> <div className="query-tags"> <QueryTagsControl tags={query.tags} isDraft={queryFlags.isDraft} isArchived={queryFlags.isArchived} canEdit={queryFlags.canEdit} getAvailableTags={getQueryTags} onEdit={updateTags} tagsExtra={tagsExtra} /> </div> </div> <div className="header-actions"> {headerExtra} {isDesktop && queryFlags.isDraft && !queryFlags.isArchived && !queryFlags.isNew && queryFlags.canEdit && ( <Button className="m-r-5" onClick={publishQuery}> <i className="fa fa-paper-plane m-r-5" /> Publish </Button> )} {!queryFlags.isNew && queryFlags.canViewSource && ( <span> {!sourceMode && ( <Button className="m-r-5" href={query.getUrl(true, selectedVisualization)}> <i className="fa fa-pencil-square-o" aria-hidden="true" /> <span className="m-l-5">Edit Source</span> </Button> )} {sourceMode && ( <Button className="m-r-5" href={query.getUrl(false, selectedVisualization)} data-test="QueryPageShowDataOnly"> <i className="fa fa-table" aria-hidden="true" /> <span className="m-l-5">Show Data Only</span> </Button> )} </span> )} {!queryFlags.isNew && ( <Dropdown overlay={moreActionsMenu} trigger={["click"]}> <Button> <Icon type="ellipsis" rotate={90} /> </Button> </Dropdown> )} </div> </div> ); } QueryPageHeader.propTypes = { query: PropTypes.shape({ id: PropTypes.number, name: PropTypes.string, tags: PropTypes.arrayOf(PropTypes.string), }).isRequired, dataSource: PropTypes.object, sourceMode: PropTypes.bool, selectedVisualization: PropTypes.number, headerExtra: PropTypes.node, tagsExtra: PropTypes.node, onChange: PropTypes.func, }; QueryPageHeader.defaultProps = { dataSource: null, sourceMode: false, selectedVisualization: null, headerExtra: null, tagsExtra: null, onChange: () => {}, };