Many of the use cases for the inputunion
are direct mirrors of the ability to leverage the interface
/ union
in a Query. We have a number of APIs that consist of a configuration side and a query side.
Note that these examples are trimmed down, the data structures are larger than what is displayed here for convienience.
We have the ability to link your New Relic account to a set of Cloud Providers to automatically gather metrics from them. There is an API to setup those configurations as well as to retrieve them.
Each different Integration has a different set of config parameters. There are a huge number of these, dozens for each AWS, Azure, Google, etc.
Ideally we'd have a nice flat list of intgrations with a symetrical return structure:
With Input Union:
mutation {
cloudConfigureIntegration (
accountId: 123,
integrations: [
{
__inputname: AwsSqsIntegrationConfig
linkedAccountId: 456
metricsPollingInterval: 20
awsRegions: ["r1", "r2"]
},
{
__inputname: AzureFunctionsIntegrationConfig
linkedAccountId: 456
metricsPollingInterval: 20
resourceGroups: ["r1", "r2"]
}
]
) {
configuredIntegrations {
__typename
name
linkedAccountId
... on AwsSqsIntegration {
awsRegions
}
... on AzureFunctionsIntegration {
resourceGroups
}
}
}
}
Without inputunion
, we have to introduce a kludge of "container" input objects that let us submit lists of things that are all of the same type. We litter our schema with possibly hundreds of objects who's only purpose is to provide a "path" to a list of a single type. The other alternative was to have a different mutation for every single type, but that would also lead to many hundreds of mutations just for this operation.. There are other operations like update
and delete
Without Input Union:
mutation {
cloudConfigureIntegration(
accountId: 123,
integrations: {
aws: {
sqs: [
{
linkedAccountId: 456
metricsPollingInterval: 20
awsRegions: ["r1", "r2"]
}
]
}
azure: {
functions: [
{
linkedAccountId: 789
resourceGroups: ["g1", "g2"]
}
]
}
}
) {
integrations {
__typename
name
... on AwsSqsIntegration {
awsRegions
}
... on AzureFunctionsIntegration {
resourceGroups
}
}
}
}
We leverage the interface
on the queries for these structures, which is nice. However it means that the data structures for mutations and queries are quite different, which is a confusing user experience.
One of our main features is dashboards, and inside a dashboard there are many "widget" types..
Ideally, the mutation to create a dashboard just contained a list of widgets, each of their own type. Widgets share some structure but have important differences based on visualization, data source, etc.
With Input Union:
mutation {
insightsCreateDashboard(
accountId: 123
dashboard: {
description: "My Cool Dashboard"
widgets: [
{
__inputname: FacetChartWidget
layout: {column: 1, row: 1}
nrqlQuery: "SELECT count(*) FROM Transaction FACET host"
},
{
__inputname: MetricChartWidget
layout: {column: 1, row: 2}
metrics: ["Errors/all"]
}
]
}
) {
dashboard {
description
widgets {
__typename
layout
.. on FacetChartWidget {
nrqlQuery
}
.. on MetricChartWidget {
metrics
}
}
}
}
}
Without inputunion
, we have to use the same hack of the container object, which leads to a fairly strange API where you have to group widgets according to their type.
The option of different mutations per type isn't feasable here, there's nothing in existance that you could addFacetWidgetToDashboard
to when you are just creating a new dashboard.
Without Input Union:
mutation {
insightsCreateDashboard(
accountId: 123
dashboard: {
description: "My Cool Dashboard"
widgets: {
facetChart: [
{
layout: {column: 1, row: 1}
nrqlQuery: "SELECT count(*) FROM Transaction FACET host"
}
],
metricChart: [
{
layout: {column: 1, row: 2}
metrics: ["Errors/all"]
}
]
}
}
) {
dashboard {
description
widgets {
facetChart {
layout {column row}
nrqlQuery
}
metricChart {
layout {column row}
metrics
}
}
}
}
}
And here we wanted to enable a user to easily query a full dashboard, transform some of it, and then update it or create a new dashboard based on it. So we had to mirror the container object structure in the query types, which means we couldn't even leverage the interface
:(
Another similar case is when we want to record some metrics of a few different types. There are counters, summaries, gauges, etc. These structures are nested deeply into a common data structure, and would be very painful to use if we had a different mutation for each of type.
mutation {
recordMetric(
startTime: 1531414060739
endTime: 1531414060739
dataPoints: [
{
group: {
__inputname: CuratedGroupType
type: TRANSACTION_SUMMARY
}
dimensions: [
{
key: "request.method"
value: "GET"
},
{
key: "httpResponseCode"
value: "200"
}
]
metrics: [
{
__inputname: CounterMetric
name: "errorCount"
ticks: 1
total: 1
},
{
__inputname: SummaryMetric
name: "duration"
count: 5
total: 0.004382655
min: 0.0005093
max: 0.001708826
}
]
}
]
) {
responseStatus
}
}
This one is interesting because there actually could be multiple uses of an inputunion
here. The group
field might be a set of known ENUM values or a custom value. I don't have a solution here - splitting this into fully typed mutations as GraphQL is now would lead to a giant explosion in the quantity of mutations.
enum GroupType {
TRANSACTION_SUMMARY
HOST_SUMMARY
}
type CuratedGroupType {
type: GroupType
}
type CustomGroupType {
type: String
}
inputunion MetricGroup = CuratedGroupType | CustomGroupType