chore: add column `Builtin` to `View` (#101)

* chore: add column `Builtin` to `View`

* chore: adjust TopN's UI

---------

Co-authored-by: yaojiping <yaojiping@infini.ltd>
This commit is contained in:
yaojp123 2025-01-24 15:56:12 +08:00 committed by GitHub
parent 6d576a7636
commit 098371729e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 62 additions and 28 deletions

View File

@ -43,7 +43,7 @@ export class SimpleSavedObject<T = unknown> {
constructor( constructor(
private client: SavedObjectsClientContract, private client: SavedObjectsClientContract,
{ id, type, version, attributes, error, references, migrationVersion, complex_fields }: SavedObjectType<T> { id, type, version, attributes, error, references, migrationVersion }: SavedObjectType<T>
) { ) {
this.id = id; this.id = id;
this.type = type; this.type = type;
@ -51,7 +51,6 @@ export class SimpleSavedObject<T = unknown> {
this.references = references || []; this.references = references || [];
this._version = version; this._version = version;
this.migrationVersion = migrationVersion; this.migrationVersion = migrationVersion;
this.attributes.complexFields = complex_fields
if (error) { if (error) {
this.error = error; this.error = error;
} }

View File

@ -140,6 +140,7 @@ export class IndexPattern implements IIndexPattern {
return this.deserializeFieldFormatMap(mapping); return this.deserializeFieldFormatMap(mapping);
}); });
this.viewName = spec.viewName || ""; this.viewName = spec.viewName || "";
this.builtin = spec.builtin;
} }
/** /**
@ -440,6 +441,7 @@ export class IndexPattern implements IIndexPattern {
fieldFormatMap, fieldFormatMap,
type: this.type, type: this.type,
typeMeta: this.typeMeta ? JSON.stringify(this.typeMeta) : undefined, typeMeta: this.typeMeta ? JSON.stringify(this.typeMeta) : undefined,
builtin: this.builtin
}; };
} }

View File

@ -358,7 +358,8 @@ export class IndexPatternsService {
sourceFilters, sourceFilters,
fieldFormatMap, fieldFormatMap,
typeMeta, typeMeta,
complexFields, complex_fields: complexFields,
builtin
}, },
type, type,
} = savedObject; } = savedObject;
@ -385,7 +386,8 @@ export class IndexPatternsService {
fields: this.fieldArrayToMap(parsedFields), fields: this.fieldArrayToMap(parsedFields),
typeMeta: parsedTypeMeta, typeMeta: parsedTypeMeta,
type, type,
complexFields: parsedComplexFields complexFields: parsedComplexFields,
builtin
}; };
}; };

View File

@ -40,6 +40,9 @@ const EditIndexPatternCont: React.FC<RouteComponentProps<{ id: string }>> = ({
if (!ip.id) { if (!ip.id) {
ip.id = props.match.params.id; ip.id = props.match.params.id;
} }
if (ip.builtin) {
props.history.push("");
}
setIndexPattern(ip); setIndexPattern(ip);
// setBreadcrumbs(getEditBreadcrumbs(ip)); // setBreadcrumbs(getEditBreadcrumbs(ip));
}); });

View File

@ -211,11 +211,18 @@ export const IndexPatternTable = ({
dataIndex: "title", dataIndex: "title",
sorter: (a: string, b: string) => sorter.string(a, b, "title"), sorter: (a: string, b: string) => sorter.string(a, b, "title"),
}, },
{
title: formatMessage({ id: "explore.createview.field.builtin" }),
dataIndex: "builtin",
render: (val) => {
return formatMessage({ id: `explore.createview.field.builtin.${val === true ? "true" : "false"}` });
},
},
{ {
title: formatMessage({ id: "table.field.actions" }), title: formatMessage({ id: "table.field.actions" }),
render: (text, record) => ( render: (text, record) => (
<div> <div>
{canSave ? ( {canSave && !record.builtin ? (
<> <>
<a {...reactRouterNavigate(history, `patterns/${record.id}`)}> <a {...reactRouterNavigate(history, `patterns/${record.id}`)}>
{formatMessage({ id: "form.button.edit" })} {formatMessage({ id: "form.button.edit" })}

View File

@ -38,8 +38,8 @@ export async function getIndexPatterns(
const id = pattern.id; const id = pattern.id;
const title = pattern.get('title'); const title = pattern.get('title');
const viewName = pattern.get('viewName'); const viewName = pattern.get('viewName');
const builtin = pattern.get('builtin');
const isDefault = defaultIndex === id; const isDefault = defaultIndex === id;
const tags = (indexPatternManagementStart as IndexPatternManagementStart).list.getIndexPatternTags( const tags = (indexPatternManagementStart as IndexPatternManagementStart).list.getIndexPatternTags(
pattern, pattern,
isDefault isDefault
@ -55,6 +55,7 @@ export async function getIndexPatterns(
// so the sorting will but the default index on top // so the sorting will but the default index on top
// or on bottom of a the table // or on bottom of a the table
sort: `${isDefault ? '0' : '1'}${title}`, sort: `${isDefault ? '0' : '1'}${title}`,
builtin,
}; };
}) })
.sort((a, b) => { .sort((a, b) => {

View File

@ -27,6 +27,9 @@ export default {
"explore.createview.field.match_rule": "Match Rule", "explore.createview.field.match_rule": "Match Rule",
"explore.createview.field.match_rule.help": "explore.createview.field.match_rule.help":
'Use (*) to match multiple indices and cannot contain spaces or characters , /, ?, ", <, >, |.', 'Use (*) to match multiple indices and cannot contain spaces or characters , /, ?, ", <, >, |.',
"explore.createview.field.builtin": "Builtin",
"explore.createview.field.builtin.true": "True",
"explore.createview.field.builtin.false": "false",
"explore.createview.status.match_index_num": "Matching {length} indices", "explore.createview.status.match_index_num": "Matching {length} indices",
"explore.createview.status.match_special_index": "explore.createview.status.match_special_index":
"The current matching rule did not match the Elasticsearch index.To match the special index, turn on the Include special index switch.", "The current matching rule did not match the Elasticsearch index.To match the special index, turn on the Include special index switch.",

View File

@ -26,6 +26,9 @@ export default {
"explore.createview.field.match_rule": "匹配规则", "explore.createview.field.match_rule": "匹配规则",
"explore.createview.field.match_rule.help": "explore.createview.field.match_rule.help":
'使用 (*) 来匹配多个索引。 不能包含空格或者字符 , /, ?, ", <, >, |.', '使用 (*) 来匹配多个索引。 不能包含空格或者字符 , /, ?, ", <, >, |.',
"explore.createview.field.builtin": "是否内置",
"explore.createview.field.builtin.true": "是",
"explore.createview.field.builtin.false": "否",
"explore.createview.status.match_index_num": "当前匹配到 {length} 个索引", "explore.createview.status.match_index_num": "当前匹配到 {length} 个索引",
"explore.createview.status.match_special_index": "explore.createview.status.match_special_index":
"当前规则没有匹配到任何索引。如需匹配特殊索引,请打开包含特殊索引开关。", "当前规则没有匹配到任何索引。如需匹配特殊索引,请打开包含特殊索引开关。",

View File

@ -36,18 +36,18 @@ export default ({ value = [], onChange, style = {}, className = '' }) => {
{ {
value.map((item, index) => ( value.map((item, index) => (
<ColorPicker key={index} color={item} onRemove={() => onRemove(index)} onChange={(color) => handleChange(color, index)}> <ColorPicker key={index} color={item} onRemove={() => onRemove(index)} onChange={(color) => handleChange(color, index)}>
<Button style={{ padding: 3, width: 120, marginBottom: 8 }} size="small" > <Button style={{ padding: 3, width: 84, marginBottom: 8 }} size="small" >
<div style={{ background: item, width: '100%', height: 16 }}></div> <div style={{ background: item, width: '100%', height: 16 }}></div>
</Button> </Button>
</ColorPicker> </ColorPicker>
)) ))
} }
<ColorPicker onChange={onAdd}> <ColorPicker onChange={onAdd}>
<Button size="small" icon="plus" style={{ width: 120 }}></Button> <Button size="small" icon="plus" style={{ width: 84 }}></Button>
</ColorPicker> </ColorPicker>
</div> </div>
)}> )}>
<div className={className} style={{ padding: '10px 8px', border: '1px solid #d9d9d9', width: 136, height: 32, cursor: 'pointer', ...style }}> <div className={className} style={{ padding: '10px 8px', border: '1px solid #d9d9d9', width: 100, height: 32, cursor: 'pointer', ...style }}>
<div style={{ background, height: 12}}></div> <div style={{ background, height: 12}}></div>
</div> </div>
</Popover> </Popover>

View File

@ -2,7 +2,7 @@
:global { :global {
.ant-popover-inner-content { .ant-popover-inner-content {
padding: 8px; padding: 8px;
max-width: 136px; max-width: 100px;
} }
.ant-popover-arrow { .ant-popover-arrow {
display: none; display: none;

View File

@ -72,8 +72,7 @@ export default (props) => {
}) })
if (res && !res.error && Array.isArray(res.saved_objects) && res.saved_objects[0]) { if (res && !res.error && Array.isArray(res.saved_objects) && res.saved_objects[0]) {
const newView = res.saved_objects[0] const newView = res.saved_objects[0]
let { fieldFormatMap, fields } = newView.attributes || {} let { fieldFormatMap, fields, complex_fields: complexFields } = newView.attributes || {}
let { complex_fields: complexFields } = newView
try { try {
fieldFormatMap = JSON.parse(fieldFormatMap) || {} fieldFormatMap = JSON.parse(fieldFormatMap) || {}
fields = JSON.parse(fields) || [] fields = JSON.parse(fields) || []
@ -413,12 +412,12 @@ export default (props) => {
<Spin spinning={loading}> <Spin spinning={loading}>
<div className={styles.topn}> <div className={styles.topn}>
<div className={styles.header}> <div className={styles.header}>
<Input.Group compact style={{ width: 'auto '}}> <Input.Group compact>
<Radio.Group <Radio.Group
value={currentMode} value={currentMode}
onChange={(e) => setCurrentMode(e.target.value)} onChange={(e) => setCurrentMode(e.target.value)}
className={styles.mode} className={styles.mode}
style={{ marginRight: 12, marginBottom: 12 }} style={{ marginRight: 10 }}
> >
<Radio.Button value="treemap"> <Radio.Button value="treemap">
<Icon <Icon
@ -439,11 +438,11 @@ export default (props) => {
/> />
</Radio.Button> </Radio.Button>
</Radio.Group> </Radio.Group>
<div className={styles.label}> <div className={styles.label} title="Top">
Top Top
</div> </div>
<InputNumber <InputNumber
style={{ width: "80px", marginBottom: 12, marginRight: 12 }} style={{ width: "60px", marginRight: 10 }}
className={styles.borderRadiusRight} className={styles.borderRadiusRight}
value={formData.top} value={formData.top}
min={1} min={1}
@ -451,13 +450,17 @@ export default (props) => {
precision={0} precision={0}
onChange={(value) => onFormDataChange({ top: value })} onChange={(value) => onFormDataChange({ top: value })}
/> />
<div className={styles.label}> <div className={styles.label} title={formatMessage({ id: "cluster.monitor.topn.area" })}>
{formatMessage({ id: "cluster.monitor.topn.area" })} {formatMessage({ id: "cluster.monitor.topn.area" })}
</div> </div>
<Select <Select
style={{ width: "150px", marginBottom: 12 }} style={{ flexGrow: 0, flexShrink: 1, overflow: 'hidden' }}
value={formData.sourceArea?.name} value={formData.sourceArea?.name}
dropdownMatchSelectWidth={false} dropdownMatchSelectWidth={false}
showSearch
filterOption={(input, option) =>
option.props?.children?.toLowerCase().indexOf(input?.toLowerCase()) >= 0
}
onChange={(value, option) => { onChange={(value, option) => {
if (value) { if (value) {
const { isComplex } = option?.props?.metric || {} const { isComplex } = option?.props?.metric || {}
@ -490,7 +493,7 @@ export default (props) => {
} }
</Select> </Select>
<Select <Select
style={{ width: "88px", marginBottom: 12, marginRight: 6 }} style={{ width: "70px", marginRight: 6 }}
className={styles.borderRadiusRight} className={styles.borderRadiusRight}
value={formData.statisticArea} value={formData.statisticArea}
dropdownMatchSelectWidth={false} dropdownMatchSelectWidth={false}
@ -507,14 +510,18 @@ export default (props) => {
}) })
} }
</Select> </Select>
<Button style={{ width: 32, marginBottom: 12, padding: 0, marginRight: 6, borderRadius: 4 }} onClick={() => onMetricExchange()}><Icon style={{ fontSize: 16 }} component={ConvertSvg}/></Button> <Button style={{ width: 32, minWidth: 32, padding: 0, marginRight: 6, borderRadius: 4 }} onClick={() => onMetricExchange()}><Icon style={{ fontSize: 16 }} component={ConvertSvg}/></Button>
<div className={styles.label}> <div className={styles.label} title={formatMessage({ id: "cluster.monitor.topn.color" })}>
{formatMessage({ id: "cluster.monitor.topn.color" })} {formatMessage({ id: "cluster.monitor.topn.color" })}
</div> </div>
<Select <Select
style={{ width: "150px", marginBottom: 12 }} style={{ flexGrow: 0, flexShrink: 1, overflow: 'hidden' }}
value={formData.sourceColor?.name} value={formData.sourceColor?.name}
dropdownMatchSelectWidth={false} dropdownMatchSelectWidth={false}
showSearch
filterOption={(input, option) =>
option.props?.children?.toLowerCase().indexOf(input?.toLowerCase()) >= 0
}
onChange={(value, option) => { onChange={(value, option) => {
if (value) { if (value) {
const { isComplex } = option?.props?.metric || {} const { isComplex } = option?.props?.metric || {}
@ -548,7 +555,7 @@ export default (props) => {
} }
</Select> </Select>
<Select <Select
style={{ width: "88px", marginBottom: 12 }} style={{ width: "70px" }}
value={formData.statisticColor} value={formData.statisticColor}
dropdownMatchSelectWidth={false} dropdownMatchSelectWidth={false}
onChange={(value) => onFormDataChange({ statisticColor: value })} onChange={(value) => onFormDataChange({ statisticColor: value })}
@ -564,17 +571,17 @@ export default (props) => {
}) })
} }
</Select> </Select>
<div className={styles.label} style={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }}> <div className={styles.label} title={formatMessage({ id: "cluster.monitor.topn.theme" })} style={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }}>
{formatMessage({ id: "cluster.monitor.topn.theme" })} {formatMessage({ id: "cluster.monitor.topn.theme" })}
</div> </div>
<GradientColorPicker className={styles.borderRadiusRight} style={{ marginRight: 12, marginBottom: 12 }} value={formData.colors || []} onChange={(value) => { <GradientColorPicker className={styles.borderRadiusRight} style={{ marginRight: 10 }} value={formData.colors || []} onChange={(value) => {
onFormDataChange({ colors: value }) onFormDataChange({ colors: value })
setConfig({ setConfig({
...cloneDeep(config), ...cloneDeep(config),
colors: value colors: value
}) })
}}/> }}/>
<Button style={{ marginBottom: 12 }} className={styles.borderRadiusLeft} type="primary" onClick={() => fetchData(type, clusterID, timeRange, formData)}>{formatMessage({ id: "form.button.apply" })}</Button> <Button className={styles.borderRadiusLeft} type="primary" onClick={() => fetchData(type, clusterID, timeRange, formData)}>{formatMessage({ id: "form.button.apply" })}</Button>
</Input.Group> </Input.Group>
</div> </div>

View File

@ -3,17 +3,20 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
flex-wrap: wrap; flex-wrap: nowrap;
margin-bottom: 12px;
:global { :global {
.ant-input-group { .ant-input-group {
display: flex !important; display: flex !important;
flex-wrap: wrap !important; flex-wrap: nowrap !important;
width: 100%;
} }
} }
.mode { .mode {
width: 84px; width: 84px;
min-width: 84px;
:global { :global {
.ant-radio-button-wrapper { .ant-radio-button-wrapper {
width: 42px; width: 42px;
@ -34,6 +37,10 @@
border-radius: 4px; border-radius: 4px;
border-top-right-radius: 0px !important; border-top-right-radius: 0px !important;
border-bottom-right-radius: 0px !important; border-bottom-right-radius: 0px !important;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex-grow: 0;
} }
} }