Skip to content

Commit

Permalink
Feat: support label filter for application list (#712)
Browse files Browse the repository at this point in the history
* Feat: support label filter for application list

Signed-off-by: Basuotian <[email protected]>

* add label fold up display in application table view

Signed-off-by: Basuotian <[email protected]>

* fix error when clear all label filter

Signed-off-by: Basuotian <[email protected]>

* add e2e test case for list application by label

Signed-off-by: Basuotian <[email protected]>

* fix swagger.json

Signed-off-by: Basuotian <[email protected]>

* use unique app label for e2e test

Signed-off-by: Basuotian <[email protected]>

* fix e2e test case by adding list app parms

Signed-off-by: Basuotian <[email protected]>

* fix some zh translation

Signed-off-by: Basuotian <[email protected]>

---------

Signed-off-by: Basuotian <[email protected]>
  • Loading branch information
basuotian authored Mar 23, 2023
1 parent 19c3496 commit c312e3f
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 15 deletions.
10 changes: 9 additions & 1 deletion e2e-test/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ var _ = Describe("Test application rest api", func() {
Project: appProject,
Description: "this is a test app",
Icon: "",
Labels: map[string]string{"test": "true"},
Labels: map[string]string{"test": "true", "labelselector": "true"},
EnvBinding: []*apisv1.EnvBinding{{Name: "dev-env"}},
Component: &apisv1.CreateComponentRequest{
Name: "webservice",
Expand All @@ -83,6 +83,14 @@ var _ = Describe("Test application rest api", func() {
Expect(cmp.Diff(appBase.Labels["test"], req.Labels["test"])).Should(BeEmpty())
})

It("Test listing applications by label", func() {
defer GinkgoRecover()
res := get("/applications?env=dev-env&labels=labelselector=true")
var apps apisv1.ListApplicationResponse
Expect(decodeResponseBody(res, &apps)).Should(Succeed())
Expect(cmp.Diff(len(apps.Applications), 1)).Should(BeEmpty())
})

It("Test listing components", func() {
defer GinkgoRecover()
res := get("/applications/" + appName + "/components")
Expand Down
7 changes: 5 additions & 2 deletions packages/velaux-ui/src/locals/Zh/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@
"Search by Project": "基于项目筛选",
"Search by Environment": "基于环境筛选",
"Search by Target": "基于交付目标筛选",
"Search by name and description etc": "基于名称、描述等筛选",
"Search by Name and Description etc": "基于名称、描述等筛选",
"Search by Label Selector": "基于标签筛选",
"Unrecoverable after deletion, are you sure to delete it?": "删除操作不可撤销,确认删除吗?",
"Please enter the Environment name": "请输入环境名称",
"Please enter the Environment alias": "请输入环境别名",
Expand Down Expand Up @@ -619,5 +620,7 @@
"contexts": "上下文参数",
"Are you sure to rerun this Pipeline?": "确定要重新运行流水线吗?",
"Show Values File": "查看 Value 文件",
"Showing the all projects that you have permissions": "你拥有权限的所有项目"
"Showing all projects you are permitted": "你拥有权限的所有项目",
"More": "显示更多",
"Hide": "收起"
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,24 @@
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.content-labels {
display: -webkit-box;
height: 70px;
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.content-foot {
padding: 16px 0 24px;
}
}
}

.table-content-label {
display: -webkit-box;
height: 25px;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { MouseEvent } from 'react';
import React from 'react';
import './index.less';
import { Link } from 'dva/router';
import { Grid, Card, Menu, Dropdown, Dialog, Button, Table } from '@alifd/next';
import { Grid, Card, Menu, Dropdown, Dialog, Button, Table, Tag, Icon } from '@alifd/next';
import { AiFillDelete, AiFillSetting } from 'react-icons/ai';

import type { ShowMode } from '../..';
Expand All @@ -23,6 +23,7 @@ const { Column } = Table;
type State = {
extendDotVisible: boolean;
choseIndex: number;
showLabelMode: Map<string, boolean>;
};

type Props = {
Expand All @@ -32,6 +33,7 @@ type Props = {
editAppPlan: (item: ApplicationBase) => void;
deleteAppPlan: (name: string) => void;
setVisible: (visible: boolean) => void;
clickLabelFilter: (label: string) => void;
showMode: ShowMode;
};

Expand All @@ -41,9 +43,19 @@ type Props = {
class CardContent extends React.Component<Props, State> {
constructor(props: any) {
super(props);
const {applications} = this.props;
let showLabelMode = new Map<string, boolean>();
applications?.map((app) => {
if (app.labels && Object.keys(app.labels).length > 1) {
showLabelMode.set(app.name, true)
} else {
showLabelMode.set(app.name, false)
}
});
this.state = {
extendDotVisible: false,
choseIndex: 0,
showLabelMode: showLabelMode,
};
}

Expand All @@ -64,6 +76,19 @@ class CardContent extends React.Component<Props, State> {
this.props.editAppPlan(item);
};

onClickLabelFilter = (label: string) => {
this.props.clickLabelFilter(label)
}

onMoreLabels = (appName: string) => {
let { showLabelMode } = this.state;
let cur = showLabelMode.get(appName);
showLabelMode.set(appName, cur? false: true);
this.setState({
showLabelMode,
});
}

isEditPermission = (item: ApplicationBase, button?: boolean) => {
const { userInfo } = this.props;
const project = item?.project?.name || this.props.projectName || '?';
Expand Down Expand Up @@ -166,7 +191,48 @@ class CardContent extends React.Component<Props, State> {
return <span>{v}</span>;
},
},

{
key: 'labels',
title: <Translation>Tags</Translation>,
dataIndex: 'labels',
cell: (label: Record<string, string>, i: number, v: ApplicationBase) => {
const { showLabelMode } = this.state
const more = showLabelMode.get(v.name)
let displayLabels = 0
return (
<div>
<div className={more? '': 'table-content-label'}>
{ Object.keys(label).map((key) => {
if (label && key.indexOf("ux.oam.dev") < 0 && key.indexOf("app.oam.dev")) {
displayLabels++
return (
<div>
<Tag
onClick={((e) => this.onClickLabelFilter(key+"="+`${label[key]}`))}
key={`${key}=${label[key]}`}
style={{ margin: '2px' }}
color="blue"
size="small"
>{`${key}=${label[key]}`}</Tag>
</div>
)
}
})}
</div>
{ displayLabels > 1 &&
<div>
<Tag
onClick={((e) => this.onMoreLabels(v.name))}
key={"showLabelTag"}
style={{ margin: '2px' }}
size="small"
><Translation>{more? "Hide": "More"}</Translation>{more? <Icon type="minus" />: <Icon type="add" />}</Tag>
</div>
}
</div>
)
},
},
{
key: 'operation',
title: <Translation>Actions</Translation>,
Expand Down Expand Up @@ -239,7 +305,7 @@ class CardContent extends React.Component<Props, State> {
return (
<Row wrap={true}>
{applications?.map((item: ApplicationBase) => {
const { name, alias, icon, description, createTime, readOnly } = item;
const { name, alias, icon, description, createTime, readOnly, labels } = item;
const showName = alias || name;
return (
<Col xl={6} m={8} s={12} xxs={24} className={`card-content-wrapper`} key={`${item.name}`}>
Expand Down Expand Up @@ -299,7 +365,21 @@ class CardContent extends React.Component<Props, State> {
{description}
</h4>
</Row>

<Row className="content-labels">
{labels &&
Object.keys(labels).map((key) => {
if (labels && key.indexOf("ux.oam.dev") < 0 && key.indexOf("app.oam.dev")) {
return (
<Tag
onClick={((e) => this.onClickLabelFilter(key+"="+`${labels[key]}`))}
key={key}
style={{ margin: '4px' }}
color="blue"
>{`${key}=${labels[key]}`}</Tag>
);
}
})}
</Row>
<Row className="content-foot colorA6A6A6">
<Col span="16">
<span>{createTime && momentDate(createTime)}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type Props = {
dispatch: ({}) => {};
projects?: Project[];
envs?: Env[];
appLabels?: string[];
labelValue?: string[];
setLabelValue: (labels: string[]) => void;
getApplications: (params: any) => void;
setMode: (mode: ShowMode) => void;
showMode: ShowMode;
Expand All @@ -25,6 +28,7 @@ type State = {
targetValue: string;
inputValue: string;
envValue: string;
labelValue: string[];
};

class SelectSearch extends React.Component<Props, State> {
Expand All @@ -35,10 +39,12 @@ class SelectSearch extends React.Component<Props, State> {
targetValue: '',
envValue: '',
inputValue: '',
labelValue: [],
};
this.onChangeProject = this.onChangeProject.bind(this);
this.onChangeTarget = this.onChangeTarget.bind(this);
this.handleChangName = this.handleChangName.bind(this);
this.handleChangeLabel = this.handleChangeLabel.bind(this);
}

onChangeProject(e: string) {
Expand Down Expand Up @@ -69,6 +75,19 @@ class SelectSearch extends React.Component<Props, State> {
});
}

handleChangeLabel(value: string[]) {
const { setLabelValue } = this.props;
let label = value? value:[]
setLabelValue(label)
this.setState({
labelValue: label,
},
() => {
this.getApplications();
});

}

onChangeEnv = (e: string) => {
this.setState(
{
Expand All @@ -85,28 +104,37 @@ class SelectSearch extends React.Component<Props, State> {
};

getApplications = async () => {
const { projectValue, inputValue, envValue } = this.state;
const { projectValue, inputValue, envValue, labelValue } = this.state;
const labelSelector = labelValue.join(",")
const params = {
project: projectValue,
query: inputValue,
env: envValue,
labels: labelSelector,
};
this.props.getApplications(params);
};

render() {
const { projects, envs, showMode } = this.props;
const { projects, appLabels, envs, showMode, labelValue } = this.props;
const { projectValue, inputValue, envValue } = this.state;

const projectPlaceholder = i18n.t('Search by Project').toString();
const appPlaceholder = i18n.t('Search by name and description etc').toString();
const envPlaceholder = i18n.t('Search by Environment').toString();
const labelPlaceholder = i18n.t('Search by labels').toString();
const projectSource = projects?.map((item) => {
return {
label: item.alias || item.name,
value: item.name,
};
});
const labelSource = appLabels?.map((item) => {
return {
label: item,
value: item,
};
});

const envSource = envs?.map((env) => {
return {
Expand All @@ -116,7 +144,7 @@ class SelectSearch extends React.Component<Props, State> {
});
return (
<Row className="app-select-wrapper border-radius-8" wrap={true}>
<Col xl={6} m={8} s={12} xxs={24} style={{ padding: '0 8px' }}>
<Col xl={6} m={4} s={6} xxs={12} style={{ padding: '0 8px' }}>
<Select
locale={locale().Select}
mode="single"
Expand All @@ -129,7 +157,7 @@ class SelectSearch extends React.Component<Props, State> {
value={projectValue}
/>
</Col>
<Col xl={6} m={8} s={12} xxs={24} style={{ padding: '0 8px' }}>
<Col xl={6} m={4} s={6} xxs={12} style={{ padding: '0 8px' }}>
<Select
locale={locale().Select}
mode="single"
Expand All @@ -142,6 +170,19 @@ class SelectSearch extends React.Component<Props, State> {
value={envValue}
/>
</Col>
<Col xl={6} m={8} s={12} xxs={24} style={{ padding: '0 8px' }}>
<Select
hasClear
size="large"
placeholder={labelPlaceholder}
onChange={this.handleChangeLabel}
showSearch
mode="multiple"
value={labelValue}
className="item"
dataSource={labelSource}
/>
</Col>
<Col xl={6} m={8} s={12} xxs={24} style={{ padding: '0 8px' }}>
<Input
innerAfter={<AiOutlineSearch onClick={this.handleClickSearch} style={{ margin: 4 }} />}
Expand Down
Loading

0 comments on commit c312e3f

Please sign in to comment.