Copilot commented on code in PR #1487: URL: https://github.com/apache/dubbo-admin/pull/1487#discussion_r3487728673
########## pkg/core/resource/apis/mesh/v1alpha1/k8sevent_types.go: ########## @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package v1alpha1 + +import ( + "encoding/json" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sruntime "k8s.io/apimachinery/pkg/runtime" + + meshproto "github.com/apache/dubbo-admin/api/mesh/v1alpha1" + "github.com/apache/dubbo-admin/pkg/core/logger" + coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model" +) + +const K8sEventKind coremodel.ResourceKind = "K8sEvent" + +func init() { + coremodel.RegisterResourceSchema(K8sEventKind, NewK8sEventResource, NewK8sEventResourceList) +} + +type K8sEventResource struct { + metav1.TypeMeta `json:",inline"` + + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Mesh is the name of the dubbo mesh this resource belongs to. + Mesh string `json:"mesh,omitempty"` + + // Spec is the specification of the K8sEvent resource. + Spec *meshproto.K8sEvent `json:"spec,omitempty"` + + // Status is the status of the K8sEvent resource. + Status K8sEventResourceStatus `json:"status,omitempty"` +} + +type K8sEventResourceStatus struct{} + +func (r *K8sEventResource) ResourceKind() coremodel.ResourceKind { + return K8sEventKind +} + +func (r *K8sEventResource) ResourceMesh() string { + return r.Mesh +} + +func (r *K8sEventResource) ResourceKey() string { + return coremodel.BuildResourceKey(r.Mesh, r.Name) +} + +func (r *K8sEventResource) ResourceMeta() metav1.ObjectMeta { + return r.ObjectMeta +} + +func (r *K8sEventResource) ResourceSpec() coremodel.ResourceSpec { + return r.Spec +} + +func (r *K8sEventResource) DeepCopyObject() k8sruntime.Object { + out := &K8sEventResource{ + TypeMeta: r.TypeMeta, + Mesh: r.Mesh, + Status: r.Status, + } + + r.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + + if r.Spec != nil { + out.Spec = r.Spec.Clone() + } + + return out +} + +func (r *K8sEventResource) String() string { + jsonStr, err := json.Marshal(r) + if err != nil { + logger.Errorf("failed to encode K8sEventResource: %s to json, err: %v", r.ResourceKey(), err) + return "" + } + return string(jsonStr) +} + +func NewK8sEventResourceWithAttributes(name string, mesh string) *K8sEventResource { + return &K8sEventResource{ + TypeMeta: metav1.TypeMeta{ + Kind: string(K8sEventKind), + APIVersion: "v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Labels: map[string]string{}, + }, + Mesh: mesh, + Spec: &meshproto.K8sEvent{}, + } +} + +func NewK8sEventResource() coremodel.Resource { + return &K8sEventResource{ + TypeMeta: metav1.TypeMeta{ + Kind: string(K8sEventKind), + APIVersion: "v1alpha1", + }, + Spec: &meshproto.K8sEvent{}, + } +} + +type K8sEventResourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []*K8sEventResource `json:"items"` +} + +func (r *K8sEventResourceList) DeepCopyObject() k8sruntime.Object { + out := &K8sEventResourceList{ + TypeMeta: r.TypeMeta, + } + r.ListMeta.DeepCopyInto(&out.ListMeta) + + if len(r.Items) == 0 { + return out + } + out.Items = make([]*K8sEventResource, len(r.Items)) + for i := range r.Items { + out.Items[i] = r.Items[i].DeepCopyObject().(*K8sEventResource) + } + return out +} + +func NewK8sEventResourceList() coremodel.ResourceList { + return &K8sEventResourceList{ + TypeMeta: metav1.TypeMeta{ + Kind: string(K8sEventKind), + APIVersion: "v1alpha1", + }, + Items: make([]*K8sEventResource, 0), + } +} + +func (r *K8sEventResourceList) SetItems(items []coremodel.Resource) { + r.Items = make([]*K8sEventResource, len(items)) + for i := range items { + res, ok := items[i].(*K8sEventResource) + if !ok { + logger.Errorf("unexpected resource type, expected: %s, get %s", K8sEventKind, res.ResourceKind()) + continue + } Review Comment: In SetItems(), when the type assertion fails, `res` is nil and calling `res.ResourceKind()` will panic. Log the actual item type instead of dereferencing `res` on the failure path. ########## pkg/console/service/event.go: ########## @@ -0,0 +1,333 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package service + +import ( + "sort" + "strings" + "time" + + consolectx "github.com/apache/dubbo-admin/pkg/console/context" + "github.com/apache/dubbo-admin/pkg/console/model" + "github.com/apache/dubbo-admin/pkg/core/manager" + meshresource "github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1" + coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model" + "github.com/apache/dubbo-admin/pkg/core/store/index" +) + +func ListApplicationEvents(ctx consolectx.Context, req *model.EventQueryReq) (*model.EventListResp, error) { + k8sEvents, err := manager.ListByIndexes[*meshresource.K8sEventResource]( + ctx.ResourceManager(), + meshresource.K8sEventKind, + buildApplicationK8sConditions(req), + ) + if err != nil { + return nil, err + } + + platformEvents, err := manager.ListByIndexes[*meshresource.PlatformEventResource]( + ctx.ResourceManager(), + meshresource.PlatformEventKind, + buildApplicationPlatformConditions(req), + ) + if err != nil { + return nil, err + } + + return buildEventListResp(req.PageReq, toApplicationEventEntries(k8sEvents, platformEvents)), nil +} + +func ListInstanceEvents(ctx consolectx.Context, req *model.EventQueryReq) (*model.EventListResp, error) { + k8sEvents, err := manager.ListByIndexes[*meshresource.K8sEventResource]( + ctx.ResourceManager(), + meshresource.K8sEventKind, + []index.IndexCondition{ + {IndexName: index.ByMeshIndex, Value: req.Mesh, Operator: index.Equals}, + }, + ) + if err != nil { + return nil, err + } + + platformEvents, err := manager.ListByIndexes[*meshresource.PlatformEventResource]( + ctx.ResourceManager(), + meshresource.PlatformEventKind, + []index.IndexCondition{ + {IndexName: index.ByMeshIndex, Value: req.Mesh, Operator: index.Equals}, + }, + ) + if err != nil { + return nil, err + } + + return buildEventListResp(req.PageReq, toInstanceEventEntries(req, k8sEvents, platformEvents)), nil +} + +func ListServiceEvents(ctx consolectx.Context, req *model.EventQueryReq) (*model.EventListResp, error) { + k8sEvents, err := manager.ListByIndexes[*meshresource.K8sEventResource]( + ctx.ResourceManager(), + meshresource.K8sEventKind, + buildServiceK8sConditions(req), + ) + if err != nil { + return nil, err + } + + platformEvents, err := manager.ListByIndexes[*meshresource.PlatformEventResource]( + ctx.ResourceManager(), + meshresource.PlatformEventKind, + buildServicePlatformConditions(req), + ) + if err != nil { + return nil, err + } + + return buildEventListResp(req.PageReq, toServiceEventEntries(k8sEvents, platformEvents)), nil +} + +type eventEntry struct { + timeValue time.Time + event *model.EventItem +} + +func buildApplicationK8sConditions(req *model.EventQueryReq) []index.IndexCondition { + conditions := []index.IndexCondition{ + {IndexName: index.ByMeshIndex, Value: req.Mesh, Operator: index.Equals}, + } + if req.AppName != "" { + conditions = append(conditions, index.IndexCondition{ + IndexName: index.ByK8sEventInvolvedObjName, + Value: req.AppName, + Operator: index.HasPrefix, + }) + } + return conditions +} + +func buildApplicationPlatformConditions(req *model.EventQueryReq) []index.IndexCondition { + conditions := []index.IndexCondition{ + {IndexName: index.ByMeshIndex, Value: req.Mesh, Operator: index.Equals}, + } + if req.AppName != "" { + conditions = append(conditions, index.IndexCondition{ + IndexName: index.ByPlatformEventAppName, + Value: req.AppName, + Operator: index.Equals, + }) + } + return conditions +} + +func buildServiceK8sConditions(req *model.EventQueryReq) []index.IndexCondition { + conditions := []index.IndexCondition{ + {IndexName: index.ByMeshIndex, Value: req.Mesh, Operator: index.Equals}, + } + if req.ServiceName != "" { + conditions = append(conditions, index.IndexCondition{ + IndexName: index.ByK8sEventInvolvedObjName, + Value: req.ServiceName, + Operator: index.Equals, + }) + } + return conditions +} + +func buildServicePlatformConditions(req *model.EventQueryReq) []index.IndexCondition { + conditions := []index.IndexCondition{ + {IndexName: index.ByMeshIndex, Value: req.Mesh, Operator: index.Equals}, + } + if req.ServiceName != "" { + conditions = append(conditions, index.IndexCondition{ + IndexName: index.ByPlatformEventServiceName, + Value: req.ServiceName, + Operator: index.Equals, + }) + } + return conditions +} + +func toApplicationEventEntries( + k8sEvents []*meshresource.K8sEventResource, + platformEvents []*meshresource.PlatformEventResource, +) []eventEntry { + result := make([]eventEntry, 0, len(k8sEvents)+len(platformEvents)) + result = append(result, toK8sEventEntries(k8sEvents)...) + result = append(result, toPlatformEventEntries(platformEvents)...) + return result +} + +func toInstanceEventEntries( + req *model.EventQueryReq, + k8sEvents []*meshresource.K8sEventResource, + platformEvents []*meshresource.PlatformEventResource, +) []eventEntry { + result := make([]eventEntry, 0, len(k8sEvents)+len(platformEvents)) + + for _, eventRes := range k8sEvents { + if eventRes == nil || eventRes.Spec == nil { + continue + } + involvedName := eventRes.Spec.InvolvedObjName + if req.InstanceName != "" || req.InstanceIP != "" { + if involvedName != req.InstanceName && involvedName != req.InstanceIP { + continue + } + } + result = append(result, eventEntry{ + timeValue: parseEventTime(eventRes.Spec.LastTimestamp), + event: &model.EventItem{ + Time: eventRes.Spec.LastTimestamp, + Type: toEventType(eventRes.Spec.Type), + Message: eventRes.Spec.Message, + Source: defaultK8sSource(eventRes), + }, + }) + } + + for _, eventRes := range platformEvents { + if eventRes == nil || eventRes.Spec == nil { + continue + } + if req.InstanceName != "" || req.InstanceIP != "" { + if eventRes.Spec.InstanceName != req.InstanceName && eventRes.Spec.InstanceIP != req.InstanceIP { + continue + } + } + result = append(result, eventEntry{ + timeValue: parseEventTime(eventRes.Spec.EventTime), + event: &model.EventItem{ + Time: eventRes.Spec.EventTime, + Type: toEventType(eventRes.Spec.Type), + Message: eventRes.Spec.Message, + Source: eventRes.Spec.Source, + }, + }) + } + + return result +} + +func toServiceEventEntries( + k8sEvents []*meshresource.K8sEventResource, + platformEvents []*meshresource.PlatformEventResource, +) []eventEntry { + result := make([]eventEntry, 0, len(k8sEvents)+len(platformEvents)) + result = append(result, toK8sEventEntries(k8sEvents)...) + result = append(result, toPlatformEventEntries(platformEvents)...) + return result +} + +func toK8sEventEntries(items []*meshresource.K8sEventResource) []eventEntry { + result := make([]eventEntry, 0, len(items)) + for _, eventRes := range items { + if eventRes == nil || eventRes.Spec == nil { + continue + } + result = append(result, eventEntry{ + timeValue: parseEventTime(eventRes.Spec.LastTimestamp), + event: &model.EventItem{ + Time: eventRes.Spec.LastTimestamp, + Type: toEventType(eventRes.Spec.Type), + Message: eventRes.Spec.Message, + Source: defaultK8sSource(eventRes), + }, + }) + } + return result +} + +func toPlatformEventEntries(items []*meshresource.PlatformEventResource) []eventEntry { + result := make([]eventEntry, 0, len(items)) + for _, eventRes := range items { + if eventRes == nil || eventRes.Spec == nil { + continue + } + result = append(result, eventEntry{ + timeValue: parseEventTime(eventRes.Spec.EventTime), + event: &model.EventItem{ + Time: eventRes.Spec.EventTime, + Type: toEventType(eventRes.Spec.Type), + Message: eventRes.Spec.Message, + Source: eventRes.Spec.Source, + }, + }) + } + return result +} + +func buildEventListResp(pageReq coremodel.PageReq, entries []eventEntry) *model.EventListResp { + sort.SliceStable(entries, func(i, j int) bool { + if entries[i].timeValue.Equal(entries[j].timeValue) { + return entries[i].event.Message > entries[j].event.Message + } + return entries[i].timeValue.After(entries[j].timeValue) + }) + + total := len(entries) + start := pageReq.PageOffset + if start > total { + start = total + } + end := start + pageReq.PageSize + if pageReq.PageSize <= 0 || end > total { + end = total + } Review Comment: buildEventListResp() does not clamp negative pageOffset; a request like `?pageOffset=-1` will cause a slice bounds panic when indexing `entries[start:end]`. Also, when pageSize <= 0 it currently returns the full list, which can unintentionally generate huge responses. Clamp offset to [0,total] and apply a safe default page size. ########## pkg/console/service/event.go: ########## @@ -0,0 +1,333 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package service + +import ( + "sort" + "strings" + "time" + + consolectx "github.com/apache/dubbo-admin/pkg/console/context" + "github.com/apache/dubbo-admin/pkg/console/model" + "github.com/apache/dubbo-admin/pkg/core/manager" + meshresource "github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1" + coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model" + "github.com/apache/dubbo-admin/pkg/core/store/index" +) + +func ListApplicationEvents(ctx consolectx.Context, req *model.EventQueryReq) (*model.EventListResp, error) { + k8sEvents, err := manager.ListByIndexes[*meshresource.K8sEventResource]( + ctx.ResourceManager(), + meshresource.K8sEventKind, + buildApplicationK8sConditions(req), + ) + if err != nil { + return nil, err + } + + platformEvents, err := manager.ListByIndexes[*meshresource.PlatformEventResource]( + ctx.ResourceManager(), + meshresource.PlatformEventKind, + buildApplicationPlatformConditions(req), + ) + if err != nil { + return nil, err + } + + return buildEventListResp(req.PageReq, toApplicationEventEntries(k8sEvents, platformEvents)), nil +} + +func ListInstanceEvents(ctx consolectx.Context, req *model.EventQueryReq) (*model.EventListResp, error) { + k8sEvents, err := manager.ListByIndexes[*meshresource.K8sEventResource]( + ctx.ResourceManager(), + meshresource.K8sEventKind, + []index.IndexCondition{ + {IndexName: index.ByMeshIndex, Value: req.Mesh, Operator: index.Equals}, + }, + ) + if err != nil { + return nil, err + } + + platformEvents, err := manager.ListByIndexes[*meshresource.PlatformEventResource]( + ctx.ResourceManager(), + meshresource.PlatformEventKind, + []index.IndexCondition{ + {IndexName: index.ByMeshIndex, Value: req.Mesh, Operator: index.Equals}, + }, + ) + if err != nil { + return nil, err + } Review Comment: ListInstanceEvents() loads *all* K8sEvent and PlatformEvent resources for the mesh and then filters in-memory by instanceName/ip. For meshes with large event volumes this will be slow and memory-heavy. Consider narrowing the store queries using the existing indexes (e.g., PlatformEventInstanceName/PlatformEventInstanceIP, and K8sEventInvolvedObjName) and merging/deduping results rather than listing the entire mesh. ########## ui-vue3/src/components/EventTimeline.vue: ########## @@ -0,0 +1,215 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. +--> +<template> + <div class="event-timeline-container"> + <a-spin :spinning="loading"> + <a-timeline mode="left" class="event-timeline"> + <a-timeline-item + v-for="(item, index) in events" + :key="index" + :color="item.type === 'warning' ? '#faad14' : '#1890ff'" + > + <!-- Time label on the left --> + <template #label> + <span class="event-time">{{ item.time }}</span> + </template> + + <!-- Custom dot --> + <template #dot> + <div class="event-dot" :class="item.type"> + <CheckCircleOutlined v-if="item.type !== 'warning'" class="dot-icon normal" /> + <WarningOutlined v-else class="dot-icon warning" /> + </div> + </template> + + <!-- Event card --> + <div class="event-card" :class="item.type"> + <span class="event-message">{{ item.message }}</span> + <a-tag :color="item.type === 'warning' ? 'orange' : 'blue'" class="event-source-tag"> + {{ item.source }} + </a-tag> + </div> + </a-timeline-item> + + <!-- Bottom hint --> + <a-timeline-item> + <template #dot> + <ArrowDownOutlined class="bottom-arrow" /> + </template> + <div class="bottom-hint"> + <span>{{ $t('eventExpiryHint') || '过期事件不会存储' }}</span> + <a-spin v-if="loadingMore" size="small" class="load-more-spinner" /> + </div> + </a-timeline-item> + </a-timeline> + <div ref="loadMoreTriggerRef" class="load-more-trigger" /> + </a-spin> + + <a-empty v-if="!loading && events.length === 0" description="暂无事件" /> + </div> +</template> + +<script setup lang="ts"> +import { onBeforeUnmount, onMounted, ref, watch } from 'vue' +import type { EventItem } from '@/types/api' +import { CheckCircleOutlined, WarningOutlined, ArrowDownOutlined } from '@ant-design/icons-vue' + +const props = defineProps<{ + events: EventItem[] + loading: boolean + loadingMore?: boolean + hasMore?: boolean +}>() + +const emit = defineEmits<{ + (e: 'loadMore'): void +}>() + +const loadMoreTriggerRef = ref<HTMLElement>() +let observer: IntersectionObserver | null = null + +const tryLoadMore = () => { + if (!props.loading && !props.loadingMore && props.hasMore) { + emit('loadMore') + } +} + +onMounted(() => { + if (!window.IntersectionObserver || !loadMoreTriggerRef.value) { + return + } + observer = new IntersectionObserver((entries) => { + if (entries.some((entry) => entry.isIntersecting)) { + tryLoadMore() + } + }) + observer.observe(loadMoreTriggerRef.value) +}) + +watch( + () => [props.hasMore, props.loading, props.loadingMore, props.events.length], + () => { + tryLoadMore() + } +) Review Comment: The watch() callback calls tryLoadMore() unconditionally whenever props change, which can trigger loadMore repeatedly without the sentinel being visible (e.g., after each page append), potentially preloading all pages and spamming requests. Gate the call on the sentinel being in/near the viewport (or rely solely on IntersectionObserver). ########## pkg/engine/kubernetes/listerwatcher/k8s_event.go: ########## @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package listerwatcher + +import ( + "reflect" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + k8sruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + + meshproto "github.com/apache/dubbo-admin/api/mesh/v1alpha1" + "github.com/apache/dubbo-admin/pkg/common/bizerror" + "github.com/apache/dubbo-admin/pkg/common/constants" + enginecfg "github.com/apache/dubbo-admin/pkg/config/engine" + "github.com/apache/dubbo-admin/pkg/core/controller" + "github.com/apache/dubbo-admin/pkg/core/logger" + meshresource "github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1" + coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model" +) + +type K8sEventListerWatcher struct { + cfg *enginecfg.Config + lw cache.ListerWatcher +} + +var _ controller.ResourceListerWatcher = &K8sEventListerWatcher{} + +func NewK8sEventListWatcher(clientset *kubernetes.Clientset, cfg *enginecfg.Config) (*K8sEventListerWatcher, error) { + lw := cache.NewListWatchFromClient( + clientset.CoreV1().RESTClient(), + "events", + metav1.NamespaceAll, + fields.Everything(), + ) + return &K8sEventListerWatcher{cfg: cfg, lw: lw}, nil +} + +func (k *K8sEventListerWatcher) List(options metav1.ListOptions) (k8sruntime.Object, error) { + return k.lw.List(options) +} + +func (k *K8sEventListerWatcher) Watch(options metav1.ListOptions) (watch.Interface, error) { + return k.lw.Watch(options) +} + +func (k *K8sEventListerWatcher) ResourceKind() coremodel.ResourceKind { + return meshresource.K8sEventKind +} + +func (k *K8sEventListerWatcher) TransformFunc() cache.TransformFunc { + return func(obj interface{}) (interface{}, error) { + k8sEvent, ok := obj.(*v1.Event) + if !ok { + return nil, bizerror.NewAssertionError("v1.Event", reflect.TypeOf(obj).Name()) + } + + mesh := constants.DefaultMesh + res := meshresource.NewK8sEventResourceWithAttributes(k8sEvent.Namespace+"/"+k8sEvent.Name, mesh) + res.Spec = &meshproto.K8sEvent{ + Namespace: k8sEvent.Namespace, + Reason: k8sEvent.Reason, + Message: k8sEvent.Message, + Type: k8sEvent.Type, + InvolvedObjKind: k8sEvent.InvolvedObject.Kind, + InvolvedObjName: k8sEvent.InvolvedObject.Name, + SourceComponent: k8sEvent.Source.Component, + SourceHost: k8sEvent.Source.Host, + FirstTimestamp: k8sEvent.FirstTimestamp.Format(constants.TimeFormatStr), + LastTimestamp: k8sEvent.LastTimestamp.Format(constants.TimeFormatStr), + Count: k8sEvent.Count, + EventSource: "KUBERNETES", + } Review Comment: Formatting FirstTimestamp/LastTimestamp unconditionally will emit a synthetic "0001-01-01..." value when the timestamp is zero, which then breaks timeline ordering and UI display. Only format non-zero timestamps (leave empty when unknown). -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
