It seems you are hitting a typical use case to implement a backend for frontend pattern where you perform on "server" some operations to satisfy "client" specific needs.
There are several factors to consider.
Pattern A
Data lifecycle
The map "icon", "type", "severity" seems to be relatively constant. Should the values change are you willing to do a new release or is it good for your use case to just act on the data level ?
Client, server and CDN are deployed in sync or your usecase allows different version between client and servers ?
In that cases, server side is the only option.
Performance
I'm working on BI and big data tools too :). Too many server side operations can be a mess but in that case i strongly suggest a load test. GraphQL can resolve with a very low footprint without relevant impact on server performance.
Separation of concerns
I have infered from your description that your application is not microservices based and has not a backend for frontend.
If you want to introduce on frontend calculation on backend, you have to keep it logically separated from the rest.
I strongly suggest you the modular monolith paradigm that allow you a good separation of concern even if you perform client side operations on server.
Pattern B
SVG Lifecycle
You said that SVG is on CDN, the resource is provided by you or is a 3rd party asset like fontawesome ?
This can be an issue if your team have different skills: an npm update should not impact on server or db activities.
Please keep in mind how to maintain the SVG and icons map.
Complexity
Doing this mapping on frontend has not a big impact on client side performance and the complexity in very low and localized.
If you are on web client, ES6 features allows you a very readable mapping on client side with map and destructuring: the "complexity" is just on last 3 lines and is performed client side so no impact on your BI server.
const resources = {
php: {
name: "Php",
type: "application",
severity: "warn",
},
"apache-server": {
name: "Apache Server",
//
},
};
const genericResource = {
id: "generic",
name: "unknown resource",
type: "application",
severity: "warn",
};
const grapQLRequest = async () =>
Promise.resolve().then(() => [
{ resource: "php", message: "some string", timestamp: "" },
{ resource: "apache-server", message: "some string", timestamp: "" },
{ resource: "new-resource", message: "some string", timestamp: "" },
]);
const handler = async () => {
const result = await grapQLRequest().then((data) =>
data.map((x) => ({
...x,
resource: {
id: x.resource,
...(resources[x.resource] || genericResource),
},
}))
);
console.log(result);
};
void handler();
Keep resources nearby to its own usages
A very common code smell is shotgun surgery, with this approach you can bring your map very close to its own usages.
That can be more readable and maintainable, you don't need to think about separation of concerns.
IMO
I have a very low knowledge about your existing architecture and patterns, and i strongly suggest you to do a non-disruptive choice for your existing environment.
In your shoes I'll probably opt for Option B with some assumptions:
- Icons are relatively stable
- Team is composed by people with specific skill
Bonus:
If the list is small you can embed it on your own artifact, otherwise you can perform async import to import the resource only if is needed.