Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync with original Master #2

Merged
merged 6 commits into from
Sep 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 0 additions & 82 deletions client/app/components/TagsList.jsx

This file was deleted.

44 changes: 38 additions & 6 deletions client/app/components/TagsList.less
Original file line number Diff line number Diff line change
@@ -1,15 +1,47 @@
@import '~@/assets/less/ant';
@import "~@/assets/less/ant";

.tags-list {
.tags-list-title {
margin: 15px 5px 5px 5px;
display: flex;
justify-content: space-between;
align-items: center;

label {
display: block;
white-space: nowrap;
margin: 0;
}

a {
display: block;
white-space: nowrap;
cursor: pointer;

.anticon {
font-size: 75%;
margin-right: 2px;
}
}
}

.ant-badge-count {
background-color: fade(@redash-gray, 10%);
color: fade(@redash-gray, 75%);
}

.ant-menu-item-selected {
.ant-badge-count {
background-color: @primary-color;
color: white;
.ant-menu.ant-menu-inline {
border: none;

.ant-menu-item {
width: 100%;
}

.ant-menu-item-selected {
.ant-badge-count {
background-color: @primary-color;
color: white;
}
}
}
}
}
107 changes: 107 additions & 0 deletions client/app/components/TagsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { map, includes, difference } from "lodash";
import React, { useState, useCallback, useEffect } from "react";
import Badge from "antd/lib/badge";
import Menu from "antd/lib/menu";
import CloseOutlinedIcon from "@ant-design/icons/CloseOutlined";
import getTags from "@/services/getTags";

import "./TagsList.less";

type Tag = {
name: string;
count?: number;
};

type TagsListProps = {
tagsUrl: string;
showUnselectAll: boolean;
onUpdate?: (selectedTags: string[]) => void;
};

function TagsList({ tagsUrl, showUnselectAll = false, onUpdate }: TagsListProps): JSX.Element | null {
const [allTags, setAllTags] = useState<Tag[]>([]);
const [selectedTags, setSelectedTags] = useState<string[]>([]);

useEffect(() => {
let isCancelled = false;

getTags(tagsUrl).then(tags => {
if (!isCancelled) {
setAllTags(tags);
}
});

return () => {
isCancelled = true;
};
}, [tagsUrl]);

const toggleTag = useCallback(
(event, tag) => {
let newSelectedTags;
if (event.shiftKey) {
// toggle tag
if (includes(selectedTags, tag)) {
newSelectedTags = difference(selectedTags, [tag]);
} else {
newSelectedTags = [...selectedTags, tag];
}
} else {
// if the tag is the only selected, deselect it, otherwise select only it
if (includes(selectedTags, tag) && selectedTags.length === 1) {
newSelectedTags = [];
} else {
newSelectedTags = [tag];
}
}

setSelectedTags(newSelectedTags);
if (onUpdate) {
onUpdate([...newSelectedTags]);
}
},
[selectedTags, onUpdate]
);

const unselectAll = useCallback(() => {
setSelectedTags([]);
if (onUpdate) {
onUpdate([]);
}
}, [onUpdate]);

if (allTags.length === 0) {
return null;
}

return (
<div className="tags-list">
<div className="tags-list-title">
<label>Tags</label>
{showUnselectAll && selectedTags.length > 0 && (
<a onClick={unselectAll}>
<CloseOutlinedIcon />
clear selection
</a>
)}
</div>

<div className="tiled">
<Menu className="invert-stripe-position" mode="inline" selectedKeys={selectedTags}>
{map(allTags, tag => (
<Menu.Item key={tag.name} className="m-0">
<a
className="d-flex align-items-center justify-content-between"
onClick={event => toggleTag(event, tag.name)}>
<span className="max-character col-xs-11">{tag.name}</span>
<Badge count={tag.count} />
</a>
</Menu.Item>
))}
</Menu>
</div>
</div>
);
}

export default TagsList;
6 changes: 4 additions & 2 deletions client/app/components/items-list/components/Sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,20 @@ ProfileImage.propTypes = {
Tags
*/

export function Tags({ url, onChange }) {
export function Tags({ url, onChange, showUnselectAll }) {
if (url === "") {
return null;
}
return (
<div className="m-b-10">
<TagsList tagsUrl={url} onUpdate={onChange} />
<TagsList tagsUrl={url} onUpdate={onChange} showUnselectAll={showUnselectAll} />
</div>
);
}

Tags.propTypes = {
url: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
showUnselectAll: PropTypes.bool,
unselectAllButtonTitle: PropTypes.string,
};
31 changes: 31 additions & 0 deletions client/app/components/visualizations/visualizationComponents.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Renderer as VisRenderer, Editor as VisEditor, updateVisualizationsSetti
import { clientConfig } from "@/services/auth";

import countriesDataUrl from "@redash/viz/lib/visualizations/choropleth/maps/countries.geo.json";
import usaDataUrl from "@redash/viz/lib/visualizations/choropleth/maps/usa-albers.geo.json";
import subdivJapanDataUrl from "@redash/viz/lib/visualizations/choropleth/maps/japan.prefectures.geo.json";

function wrapComponentWithSettings(WrappedComponent) {
Expand All @@ -17,10 +18,40 @@ function wrapComponentWithSettings(WrappedComponent) {
countries: {
name: "Countries",
url: countriesDataUrl,
fieldNames: {
name: "Short name",
name_long: "Full name",
abbrev: "Abbreviated name",
iso_a2: "ISO code (2 letters)",
iso_a3: "ISO code (3 letters)",
iso_n3: "ISO code (3 digits)",
},
},
usa: {
name: "USA",
url: usaDataUrl,
fieldNames: {
name: "Name",
ns_code: "National Standard ANSI Code (8-character)",
geoid: "Geographic ID",
usps_abbrev: "USPS Abbreviation",
fips_code: "FIPS Code (2-character)",
},
},
subdiv_japan: {
name: "Japan/Prefectures",
url: subdivJapanDataUrl,
fieldNames: {
name: "Name",
name_alt: "Name (alternative)",
name_local: "Name (local)",
iso_3166_2: "ISO-3166-2",
postal: "Postal Code",
type: "Type",
type_en: "Type (EN)",
region: "Region",
region_code: "Region Code",
},
},
},
...pick(clientConfig, [
Expand Down
2 changes: 1 addition & 1 deletion client/app/pages/dashboards/DashboardList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class DashboardList extends React.Component {
onChange={controller.updateSearch}
/>
<Sidebar.Menu items={this.sidebarMenu} selected={controller.params.currentPage} />
<Sidebar.Tags url="api/dashboards/tags" onChange={controller.updateSelectedTags} />
<Sidebar.Tags url="api/dashboards/tags" onChange={controller.updateSelectedTags} showUnselectAll />
</Layout.Sidebar>
<Layout.Content>
<div data-test="DashboardLayoutContent">
Expand Down
2 changes: 1 addition & 1 deletion client/app/pages/queries-list/QueriesList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class QueriesList extends React.Component {
onChange={controller.updateSearch}
/>
<Sidebar.Menu items={this.sidebarMenu} selected={controller.params.currentPage} />
<Sidebar.Tags url="api/queries/tags" onChange={controller.updateSelectedTags} />
<Sidebar.Tags url="api/queries/tags" onChange={controller.updateSelectedTags} showUnselectAll />
</Layout.Sidebar>
<Layout.Content>
{controller.isLoaded && controller.isEmpty ? (
Expand Down
17 changes: 15 additions & 2 deletions client/app/pages/queries/hooks/useDuplicateQuery.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { noop } from "lodash";
import { noop, extend, pick } from "lodash";
import { useCallback, useState } from "react";
import url from "url";
import qs from "query-string";
import { Query } from "@/services/query";

function keepCurrentUrlParams(targetUrl) {
const currentUrlParams = qs.parse(window.location.search);
targetUrl = url.parse(targetUrl);
const targetUrlParams = qs.parse(targetUrl.search);
return url.format(
extend(pick(targetUrl, ["protocol", "auth", "host", "pathname", "hash"]), {
search: qs.stringify(extend(currentUrlParams, targetUrlParams)),
})
);
}

export default function useDuplicateQuery(query) {
const [isDuplicating, setIsDuplicating] = useState(false);

Expand All @@ -16,7 +29,7 @@ export default function useDuplicateQuery(query) {
setIsDuplicating(true);
Query.fork({ id: query.id })
.then(newQuery => {
tab.location = newQuery.getUrl(true);
tab.location = keepCurrentUrlParams(newQuery.getUrl(true));
})
.finally(() => {
setIsDuplicating(false);
Expand Down
5 changes: 5 additions & 0 deletions client/app/services/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import debug from "debug";
import { includes, extend } from "lodash";
import location from "@/services/location";
import { axios } from "@/services/axios";
import { notifySessionRestored } from "@/services/restoreSession";

export const currentUser = {
canEdit(object) {
Expand Down Expand Up @@ -46,6 +47,9 @@ export const Auth = {
isAuthenticated() {
return session.loaded && session.user.id;
},
getLoginUrl() {
return AuthUrls.Login;
},
setLoginUrl(loginUrl) {
AuthUrls.Login = loginUrl;
},
Expand Down Expand Up @@ -94,6 +98,7 @@ export const Auth = {
.then(() => {
if (Auth.isAuthenticated()) {
logger("Loaded session");
notifySessionRestored();
return session;
}
logger("Need to login, redirecting");
Expand Down
Loading