Skip to content
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,5 @@ sw.*
.output

# Claude
.claude/
.claude/
docs/
101 changes: 4 additions & 97 deletions app/components/data-stores/DataStoreList.vue
Original file line number Diff line number Diff line change
@@ -1,105 +1,12 @@
<script lang="ts" setup>
import { watchEffect } from "vue";
import { getDataStores, getProjects } from "~/composables/useAPIFetch";
import { formatDataRow } from "~/utils/format-data-row";
import type { ModifiedDetailedService } from "~/services/modifiedApiInterfaces";
import type {
DetailedAnalysis,
ListServices,
Project,
Route,
} from "~/services/Api";
import { useDataStoreList } from "~/composables/useDataStoreList";
import DetailedDataStoreTable from "~/components/data-stores/DetailedDataStoreTable.vue";

const dataStores = ref<ModifiedDetailedService[]>([]);
const projectNameMap = ref<Map<string, string | undefined>>(
new Map<string, string | undefined>(),
);

const loading = ref(true);

const dataRowUnixCols = ["created_at", "updated_at"];
const expandRowEntries = [];

const {
data: dsResp,
status: dsStatus,
error: dsError,
refresh: dsRefresh,
} = await getDataStores(true, { lazy: true });

const { data: projectResp } = await getProjects({ lazy: true });

watchEffect(() => {
if (dsResp.value?.data) {
const dataStoreData = dsResp.value.data as ListServices;
loadDetailedDataStoreTable(dataStoreData, dsStatus.value, dsError);
}
});

watchEffect(() => {
if (projectResp.value) {
const projects = projectResp.value || [];
projectNameMap.value = mapDataFromHub(projects);
}
});

async function loadDetailedDataStoreTable(
responseData: ListServices,
status: string,
error,
) {
if (status === "success") {
let formattedDataStores = formatDataRow(
responseData,
dataRowUnixCols,
expandRowEntries,
) as ModifiedDetailedService[];

formattedDataStores = formattedDataStores.filter(
(store: ModifiedDetailedService) => store.name !== "kong-admin-service",
);

formattedDataStores.forEach((store: ModifiedDetailedService) => {
if (store.routes) {
store.routes = formatDataRow(
store.routes,
dataRowUnixCols,
expandRowEntries,
);
store.routes?.forEach((proj: Route) => {
proj["projectId"] = extractProjectIdFromPath(proj.paths! as string[]);
});
}
});
dataStores.value = formattedDataStores;
} else if (error.value?.statusCode === 500) {
dataStores.value = [];
}
loading.value = false;
}

function mapDataFromHub(hubData: Project[] | DetailedAnalysis[]) {
let mappedNames = new Map<string, string | undefined>();
if (hubData && hubData.length > 0) {
hubData.forEach((entry: Project | DetailedAnalysis) => {
if (entry.id) {
mappedNames.set(entry.id, entry.name ? entry.name : "N/A");
}
});
}
return mappedNames;
}

function extractProjectIdFromPath(paths: string[]): string {
return paths[0].split("/")[1];
}
const { dataStores, projectNameMap, loading, refresh } = await useDataStoreList();

function onDeleteDataStore(dsName: string) {
dataStores.value = dataStores.value.filter(
(store: ModifiedDetailedService) => store.name !== dsName,
);
dsRefresh();
dataStores.value = dataStores.value.filter((store) => store.name !== dsName);
refresh();
Comment on lines +8 to +9
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid overwriting the optimistic delete with an immediate refresh.

refresh() causes useDataStoreList() to repopulate dataStores.value from the server. If the deletion has not propagated yet, the just-filtered row gets written back and the table shows the deleted store again. Refresh only after the delete is confirmed, or keep a pending-deletions filter when applying fresh data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/data-stores/DataStoreList.vue` around lines 8 - 9, You are
optimistically removing an item by filtering dataStores.value then immediately
calling refresh(), which repopulates dataStores from the server and can
reintroduce the just-deleted store; instead, either delay calling refresh()
until the delete API confirms success (call refresh() in the delete promise's
success handler) or maintain a pending-deletions set (e.g., track dsName in a
pendingDeletes collection used to filter incoming data in useDataStoreList) so
that refresh() respects optimistic removals until the server acknowledges
deletion; update the code paths around dataStores.value, refresh(),
useDataStoreList(), and the delete handler to implement one of these approaches.

}
</script>

Expand Down
Loading
Loading