diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 88adf46f..61144818 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -17,6 +17,7 @@ /plugins/Huntress/ @Deenk /plugins/IncidentIO/ @noorulkhan-n /plugins/Ivanti-ITSM/ @shawn149 +/plugins/LaunchDarkly/ @clarkd /plugins/MetOffice/ @blackgrouse /plugins/Notion/ @clarkd /plugins/Phare/ @vinbab diff --git a/plugins/LaunchDarkly/v1/configValidation.json b/plugins/LaunchDarkly/v1/configValidation.json new file mode 100644 index 00000000..81df23ce --- /dev/null +++ b/plugins/LaunchDarkly/v1/configValidation.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "displayName": "Authenticate", + "dataStream": { "name": "callerIdentity" }, + "required": true, + "error": "Could not authenticate with LaunchDarkly. Check that the access token is valid and that the selected instance matches the one you sign in to.", + "success": "Connected to LaunchDarkly successfully." + } + ] +} diff --git a/plugins/LaunchDarkly/v1/correlationRules/relate-environment-to-project.json b/plugins/LaunchDarkly/v1/correlationRules/relate-environment-to-project.json new file mode 100644 index 00000000..83faf15f --- /dev/null +++ b/plugins/LaunchDarkly/v1/correlationRules/relate-environment-to-project.json @@ -0,0 +1,7 @@ +{ + "displayName": "Project contains Environment", + "source": { "types": "Environment" }, + "target": { "types": "Project" }, + "conditions": [{ "sourceProperty": "projectKey", "operator": "equals", "targetProperty": "rawId" }], + "labels": { "forward": "in", "reverse": "contains" } +} diff --git a/plugins/LaunchDarkly/v1/correlationRules/relate-flag-to-project.json b/plugins/LaunchDarkly/v1/correlationRules/relate-flag-to-project.json new file mode 100644 index 00000000..7b8ecf23 --- /dev/null +++ b/plugins/LaunchDarkly/v1/correlationRules/relate-flag-to-project.json @@ -0,0 +1,7 @@ +{ + "displayName": "Project contains Flag", + "source": { "types": "Flag" }, + "target": { "types": "Project" }, + "conditions": [{ "sourceProperty": "projectKey", "operator": "equals", "targetProperty": "rawId" }], + "labels": { "forward": "in", "reverse": "contains" } +} diff --git a/plugins/LaunchDarkly/v1/custom_types.json b/plugins/LaunchDarkly/v1/custom_types.json new file mode 100644 index 00000000..7c12651d --- /dev/null +++ b/plugins/LaunchDarkly/v1/custom_types.json @@ -0,0 +1,23 @@ +[ + { + "name": "Project", + "sourceType": "Project", + "icon": "folder", + "singular": "Project", + "plural": "Projects" + }, + { + "name": "Environment", + "sourceType": "Environment", + "icon": "layer-group", + "singular": "Environment", + "plural": "Environments" + }, + { + "name": "Flag", + "sourceType": "Flag", + "icon": "flag", + "singular": "Flag", + "plural": "Flags" + } +] diff --git a/plugins/LaunchDarkly/v1/dataStreams/accountUsage.json b/plugins/LaunchDarkly/v1/dataStreams/accountUsage.json new file mode 100644 index 00000000..710e56bb --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/accountUsage.json @@ -0,0 +1,67 @@ +{ + "name": "accountUsage", + "displayName": "Account Usage", + "description": "Account usage over time, such as monthly active users, contexts or SDK service connections", + "tags": ["Usage"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "usage/{{metric}}", + "getArgs": [ + { "key": "from", "value": "{{timeframe.unixStart * 1000}}" }, + { "key": "to", "value": "{{timeframe.unixEnd * 1000}}" }, + { "key": "groupBy", "value": "{{groupBy || null}}" }, + { + "key": "projectKey", + "value": "{{(project && project[0] && project[0].rawId && project[0].rawId[0]) || null}}" + } + ], + "headers": [{ "key": "LD-API-Version", "value": "beta" }], + "postRequestScript": "accountUsage.js" + }, + "matches": "none", + "ui": [ + { + "type": "choiceChips", + "name": "metric", + "label": "Metric", + "defaultValue": "total-mau", + "validation": { "required": true }, + "options": [ + { "value": "total-mau", "label": "Monthly active users" }, + { "value": "total-contexts", "label": "Contexts" }, + { "value": "service-connections", "label": "Service connections" } + ] + }, + { + "type": "choiceChips", + "name": "groupBy", + "label": "Group by (optional)", + "options": [ + { "value": "projectId", "label": "Project" }, + { "value": "environmentId", "label": "Environment" }, + { "value": "sdkName", "label": "SDK" } + ] + }, + { + "type": "objects", + "name": "project", + "label": "Project (optional)", + "matches": { "sourceType": { "type": "oneOf", "values": ["Project"] } } + } + ], + "metadata": [ + { "name": "time", "displayName": "Time", "shape": "date", "role": "timestamp" }, + { "name": "group", "displayName": "Group", "role": "label" }, + { "name": "value", "displayName": "Value", "shape": "number", "role": "value" } + ], + "timeframes": [ + "last7days", + "last30days", + "thisMonth", + "lastMonth", + "thisQuarter", + "lastQuarter", + "thisYear" + ] +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/approvalRequests.json b/plugins/LaunchDarkly/v1/dataStreams/approvalRequests.json new file mode 100644 index 00000000..beedb5a9 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/approvalRequests.json @@ -0,0 +1,78 @@ +{ + "name": "approvalRequests", + "displayName": "Approval Requests", + "description": "Approval requests for flag changes, with their review status", + "tags": ["Activity", "Approvals"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "approval-requests", + "getArgs": [ + { + "key": "filter", + "value": "{{reviewStatus && reviewStatus !== 'all' ? 'reviewStatus anyOf [\"' + reviewStatus + '\"]' : null}}" + } + ], + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "offset": { + "mode": "row", + "rowCountIn": { "realm": "payloadArraySize", "path": "items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "offset" } + }, + "postRequestScript": "approvalRequests.js" + }, + "ui": [ + { + "name": "reviewStatus", + "label": "Review status", + "type": "radio", + "defaultValue": "all", + "options": [ + { "value": "all", "label": "All" }, + { "value": "pending", "label": "Pending" }, + { "value": "approved", "label": "Approved" }, + { "value": "declined", "label": "Declined" } + ] + } + ], + "matches": "none", + "metadata": [ + { "name": "approvalId", "displayName": "Approval ID", "visible": false }, + { "name": "description", "displayName": "Description", "role": "label" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" }, + { + "name": "reviewState", + "displayName": "Review Health", + "shape": [ + "state", + { + "map": { + "success": ["Approved"], + "warning": ["Pending"], + "error": ["Declined"] + } + } + ] + }, + { "name": "reviewStatus", "displayName": "Review Status" }, + { "name": "status", "displayName": "Status" }, + { "name": "requestorId", "displayName": "Requestor ID" }, + { "name": "projectKey", "displayName": "Project" }, + { "name": "environmentKey", "displayName": "Environment" }, + { "name": "resourceKind", "displayName": "Resource Kind" }, + { "name": "flagId", "displayName": "Flag ID", "visible": false }, + { "sourceId": "flagId", "sourceType": "Flag", "name": "flagKey" }, + { "name": "flagKey", "displayName": "Flag" }, + { "name": "instructions", "displayName": "Changes" }, + { "name": "executionDate", "displayName": "Scheduled For", "shape": "date" }, + { "name": "appliedDate", "displayName": "Applied", "shape": "date" }, + { "name": "reviewers", "displayName": "Reviewers", "shape": "number" }, + { "name": "approvals", "displayName": "Approvals", "shape": "number" }, + { "name": "declines", "displayName": "Declines", "shape": "number" } + ], + "timeframes": false +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/auditLog.json b/plugins/LaunchDarkly/v1/dataStreams/auditLog.json new file mode 100644 index 00000000..68cf03ec --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/auditLog.json @@ -0,0 +1,66 @@ +{ + "name": "auditLog", + "displayName": "Audit Log", + "description": "Changes made in LaunchDarkly, such as flag updates, with who made them", + "tags": ["Activity", "Audit"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "auditlog", + "getArgs": [ + { "key": "after", "value": "{{timeframe.unixStart * 1000}}" }, + { + "key": "spec", + "value": "{{ (() => { const f = flag && flag[0]; const e = environment && environment[0]; const p = typeof project !== 'undefined' && project && project[0]; const u = (v) => (Array.isArray(v) ? v[0] : v); if (f) { return 'proj/' + u(f.projectKey) + ':env/' + ((e && u(e.environmentKey)) || '*') + ':flag/' + u(f.flagKey); } if (e) { return 'proj/' + u(e.projectKey) + ':env/' + u(e.environmentKey) + ':flag/*'; } if (p) { return 'proj/' + u(p.rawId) + ':env/*:flag/*'; } return null; })() }}" + } + ], + "paging": { + "mode": "token", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "20" }, + "in": { "realm": "payload", "path": "items.19.date" }, + "out": { "realm": "queryArg", "path": "before" } + }, + "postRequestScript": "auditLog.js" + }, + "ui": [ + { + "type": "objects", + "name": "project", + "label": "Project (optional)", + "matches": { "sourceType": { "type": "oneOf", "values": ["Project"] } } + }, + { + "type": "objects", + "name": "flag", + "label": "Flag (optional)", + "matches": { "sourceType": { "type": "oneOf", "values": ["Flag"] } } + }, + { + "type": "objects", + "name": "environment", + "label": "Environment (optional)", + "matches": { "sourceType": { "type": "oneOf", "values": ["Environment"] } } + } + ], + "matches": "none", + "metadata": [ + { "name": "id", "displayName": "ID", "visible": false }, + { "name": "date", "displayName": "Date", "shape": "date", "role": "timestamp" }, + { "name": "title", "displayName": "Change", "role": "label" }, + { "name": "description", "displayName": "Details" }, + { "name": "kind", "displayName": "Resource Type" }, + { "name": "action", "displayName": "Action" }, + { "name": "member", "displayName": "Changed By" }, + { "name": "project", "displayName": "Project" }, + { "name": "environment", "displayName": "Environment" }, + { "name": "flagId", "displayName": "Flag ID", "visible": false }, + { "sourceId": "flagId", "sourceType": "Flag", "name": "flagKey" }, + { "name": "flagKey", "displayName": "Flag" }, + { + "name": "link", + "displayName": "Link", + "shape": ["url", { "label": "Open in LaunchDarkly" }] + } + ], + "timeframes": true +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/callerIdentity.json b/plugins/LaunchDarkly/v1/dataStreams/callerIdentity.json new file mode 100644 index 00000000..6fc37691 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/callerIdentity.json @@ -0,0 +1,17 @@ +{ + "name": "callerIdentity", + "displayName": "Caller Identity", + "description": "The account and access token used by this data source", + "tags": ["Accounts"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { "httpMethod": "get", "endpointPath": "caller-identity" }, + "matches": "none", + "metadata": [ + { "name": "accountId", "displayName": "Account ID", "role": "value" }, + { "name": "tokenName", "displayName": "Token Name", "role": "label" }, + { "name": "tokenKind", "displayName": "Token Kind" }, + { "name": "serviceToken", "displayName": "Service Token", "shape": "boolean" } + ], + "timeframes": false, + "visibility": { "type": "hidden" } +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/environments.json b/plugins/LaunchDarkly/v1/dataStreams/environments.json new file mode 100644 index 00000000..1222c533 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/environments.json @@ -0,0 +1,34 @@ +{ + "name": "environments", + "displayName": "Environments", + "description": "Environments in a LaunchDarkly project", + "tags": ["Organization"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "projects/{{object.rawId}}/environments", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "offset": { + "mode": "row", + "rowCountIn": { "realm": "payloadArraySize", "path": "items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "offset" } + }, + "postRequestScript": "environments.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Project"] } }, + "metadata": [ + { "name": "environmentId", "displayName": "Environment ID", "visible": false }, + { "sourceId": "environmentId", "sourceType": "Environment", "name": "name" }, + { "name": "name", "displayName": "Name", "role": "label" }, + { "name": "environmentName", "displayName": "Environment", "visible": false }, + { "name": "environmentKey", "displayName": "Key" }, + { "name": "projectKey", "displayName": "Project Key" }, + { "name": "critical", "displayName": "Critical", "shape": "boolean" }, + { "name": "tagList", "displayName": "Tags" } + ], + "timeframes": false +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/flagEnvironments.json b/plugins/LaunchDarkly/v1/dataStreams/flagEnvironments.json new file mode 100644 index 00000000..0a35cd6d --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/flagEnvironments.json @@ -0,0 +1,33 @@ +{ + "name": "flagEnvironments", + "displayName": "Flag Environments", + "description": "State of a feature flag in each environment of its project", + "tags": ["Status", "Configuration"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "flags/{{object.projectKey}}/{{object.flagKey}}", + "postRequestScript": "flagEnvironments.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Flag"] } }, + "metadata": [ + { "name": "environmentId", "displayName": "Environment ID", "visible": false }, + { "name": "environment", "displayName": "Environment", "role": "label" }, + { "name": "environmentKey", "displayName": "Key" }, + { "name": "on", "displayName": "On", "shape": "boolean" }, + { + "name": "status", + "displayName": "Status", + "shape": ["state", { "map": { "success": ["On"], "unknown": ["Off"] } }] + }, + { "name": "servingVariation", "displayName": "Serving Variation" }, + { "name": "offVariation", "displayName": "Off Variation" }, + { "name": "rules", "displayName": "Rules", "shape": "number" }, + { "name": "targets", "displayName": "Targets", "shape": "number" }, + { "name": "prerequisites", "displayName": "Prerequisites", "shape": "number" }, + { "name": "lastModified", "displayName": "Last Modified", "shape": "date" }, + { "name": "version", "displayName": "Version", "shape": "number" }, + { "sourceId": "environmentId", "sourceType": "Environment", "name": "environment" } + ], + "timeframes": false +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/flagEvaluations.json b/plugins/LaunchDarkly/v1/dataStreams/flagEvaluations.json new file mode 100644 index 00000000..21eda0bb --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/flagEvaluations.json @@ -0,0 +1,33 @@ +{ + "name": "flagEvaluations", + "displayName": "Flag Evaluations", + "description": "Evaluation counts for a feature flag over time, by variation", + "tags": ["Usage", "Analytics"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "usage/evaluations/{{object.projectKey}}/{{environmentKey || 'production'}}/{{object.flagKey}}", + "getArgs": [ + { "key": "from", "value": "{{timeframe.unixStart * 1000}}" }, + { "key": "to", "value": "{{timeframe.unixEnd * 1000}}" } + ], + "headers": [{ "key": "LD-API-Version", "value": "beta" }], + "postRequestScript": "flagEvaluations.js" + }, + "ui": [ + { + "name": "environmentKey", + "label": "Environment key", + "type": "text", + "defaultValue": "production", + "placeholder": "production" + } + ], + "matches": { "sourceType": { "type": "oneOf", "values": ["Flag"] } }, + "metadata": [ + { "name": "time", "displayName": "Time", "shape": "date", "role": "timestamp" }, + { "name": "variation", "displayName": "Variation", "role": "label" }, + { "name": "evaluations", "displayName": "Evaluations", "shape": "number", "role": "value" } + ], + "timeframes": true +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/flagList.json b/plugins/LaunchDarkly/v1/dataStreams/flagList.json new file mode 100644 index 00000000..6db681f1 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/flagList.json @@ -0,0 +1,37 @@ +{ + "name": "flagList", + "displayName": "Flag List", + "description": "Feature flags in a LaunchDarkly project (excludes archived flags)", + "tags": ["Inventory"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "flags/{{object.rawId}}", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "offset": { + "mode": "row", + "rowCountIn": { "realm": "payloadArraySize", "path": "items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "offset" } + }, + "postRequestScript": "flagList.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Project"] } }, + "metadata": [ + { "name": "flagId", "displayName": "Flag ID", "visible": false }, + { "sourceId": "flagId", "sourceType": "Flag", "name": "name" }, + { "name": "name", "displayName": "Name", "role": "label" }, + { "name": "flagKey", "displayName": "Key" }, + { "name": "projectKey", "displayName": "Project Key" }, + { "name": "kind", "displayName": "Kind" }, + { "name": "temporary", "displayName": "Temporary", "shape": "boolean" }, + { "name": "deprecated", "displayName": "Deprecated", "shape": "boolean" }, + { "name": "tagList", "displayName": "Tags" }, + { "name": "maintainer", "displayName": "Maintainer" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" } + ], + "timeframes": false +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/flagStatuses.json b/plugins/LaunchDarkly/v1/dataStreams/flagStatuses.json new file mode 100644 index 00000000..16a3a79e --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/flagStatuses.json @@ -0,0 +1,48 @@ +{ + "name": "flagStatuses", + "displayName": "Flag Statuses", + "description": "Lifecycle status of each feature flag in an environment (new, active, inactive or launched)", + "tags": ["Status", "Health"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "flag-statuses/{{object.projectKey}}/{{object.environmentKey}}", + "paging": { "mode": "none" }, + "postRequestScript": "flagStatuses.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Environment"] } }, + "metadata": [ + { "name": "flagId", "displayName": "Flag ID", "visible": false }, + { + "name": "flagName", + "displayName": "Name", + "role": "label", + "sourceId": "flagId", + "sourceType": "Flag", + "objectPropertyPath": "name" + }, + { "name": "flagKey", "displayName": "Key" }, + { "name": "environmentId", "displayName": "Environment ID", "visible": false }, + { "name": "environment", "displayName": "Environment" }, + { "name": "environmentLabel", "displayName": "Project / Environment" }, + { "name": "project", "displayName": "Project" }, + { "name": "status", "displayName": "Status" }, + { + "name": "health", + "displayName": "Health", + "shape": [ + "state", + { + "map": { + "success": ["active", "launched"], + "warning": ["inactive"], + "unknown": ["new"] + } + } + ] + }, + { "name": "lastRequested", "displayName": "Last Requested", "shape": "date" }, + { "name": "default", "displayName": "Default Value" } + ], + "timeframes": false +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/flags.json b/plugins/LaunchDarkly/v1/dataStreams/flags.json new file mode 100644 index 00000000..d81fe906 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/flags.json @@ -0,0 +1,53 @@ +{ + "name": "flags", + "displayName": "Flags", + "description": "Feature flags and their state in an environment", + "tags": ["Inventory", "Status"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "flags/{{object.projectKey}}", + "getArgs": [{ "key": "env", "value": "{{object.environmentKey}}" }], + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "offset": { + "mode": "row", + "rowCountIn": { "realm": "payloadArraySize", "path": "items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "offset" } + }, + "postRequestScript": "flags.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Environment"] } }, + "metadata": [ + { "name": "flagId", "displayName": "Flag ID", "visible": false }, + { "sourceId": "flagId", "sourceType": "Flag", "name": "name" }, + { "name": "name", "displayName": "Name", "role": "label" }, + { "name": "flagKey", "displayName": "Key" }, + { "name": "environmentId", "displayName": "Environment ID", "visible": false }, + { "name": "environment", "displayName": "Environment" }, + { "name": "environmentLabel", "displayName": "Project / Environment" }, + { "name": "project", "displayName": "Project" }, + { "name": "on", "displayName": "On", "shape": "boolean" }, + { + "name": "status", + "displayName": "Status", + "shape": ["state", { "map": { "success": ["On"], "unknown": ["Off"] } }] + }, + { "name": "servingVariation", "displayName": "Serving" }, + { "name": "offVariation", "displayName": "Off Variation" }, + { "name": "rules", "displayName": "Rules", "shape": "number" }, + { "name": "targets", "displayName": "Targets", "shape": "number" }, + { "name": "prerequisites", "displayName": "Prerequisites", "shape": "number" }, + { "name": "lastModified", "displayName": "Last Modified", "shape": "date" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" }, + { "name": "kind", "displayName": "Kind" }, + { "name": "temporary", "displayName": "Temporary", "shape": "boolean" }, + { "name": "tagList", "displayName": "Tags" }, + { "name": "maintainer", "displayName": "Maintainer" }, + { "name": "version", "displayName": "Version", "shape": "number" } + ], + "timeframes": false +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/projects.json b/plugins/LaunchDarkly/v1/dataStreams/projects.json new file mode 100644 index 00000000..2555c46c --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/projects.json @@ -0,0 +1,29 @@ +{ + "name": "projects", + "displayName": "Projects", + "description": "LaunchDarkly projects in the account", + "tags": ["Organization"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "projects", + "pathToData": "items", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "offset": { + "mode": "row", + "rowCountIn": { "realm": "payloadArraySize", "path": "items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "offset" } + } + }, + "matches": "none", + "metadata": [ + { "name": "key", "displayName": "Key", "role": "value" }, + { "name": "name", "displayName": "Name", "role": "label" } + ], + "timeframes": false, + "visibility": { "type": "hidden" } +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/scheduledChanges.json b/plugins/LaunchDarkly/v1/dataStreams/scheduledChanges.json new file mode 100644 index 00000000..03414694 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/scheduledChanges.json @@ -0,0 +1,37 @@ +{ + "name": "scheduledChanges", + "displayName": "Scheduled Changes", + "description": "Upcoming scheduled changes to a feature flag in an environment", + "tags": ["Activity", "Configuration"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "projects/{{object.projectKey}}/flags/{{object.flagKey}}/environments/{{environmentKey || 'production'}}/scheduled-changes", + "postRequestScript": "scheduledChanges.js" + }, + "ui": [ + { + "name": "environmentKey", + "label": "Environment key", + "type": "text", + "defaultValue": "production", + "placeholder": "production" + } + ], + "matches": { "sourceType": { "type": "oneOf", "values": ["Flag"] } }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string", "visible": false }, + { + "name": "executionDate", + "displayName": "Execution Date", + "shape": "date", + "role": "timestamp" + }, + { "name": "change", "displayName": "Change", "shape": "string", "role": "label" }, + { "name": "environment", "displayName": "Environment", "shape": "string" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" }, + { "name": "creator", "displayName": "Creator ID", "shape": "string" }, + { "name": "conflicts", "displayName": "Conflicts", "shape": "number" } + ], + "timeframes": false +} diff --git a/plugins/LaunchDarkly/v1/dataStreams/scripts/accountUsage.js b/plugins/LaunchDarkly/v1/dataStreams/scripts/accountUsage.js new file mode 100644 index 00000000..1dac69b7 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/scripts/accountUsage.js @@ -0,0 +1,36 @@ +// The API returns one wide row per time bucket: { time, "0": n, "1": n, ... }, where each numeric +// key is an index into metadata[] describing that series by its grouping dimensions, e.g. +// { projectId, projectKey, projectName } for groupBy=projectId. With no groupBy there is a single +// series whose metadata entry is empty. Unpivot into one row per (time, group). +const display = (value) => (typeof value === "string" ? value : JSON.stringify(value)); + +// One label part per dimension, preferring its Name, then Key, then Id (projectName > projectKey > projectId). +const labelFor = (key) => { + const entry = (data?.metadata || [])[Number(key)]; + if (!entry || typeof entry !== "object") { + return "Total"; + } + const dimensions = {}; + for (const [field, value] of Object.entries(entry)) { + if (field === "_links" || value === undefined || value === null || value === "") { + continue; + } + const match = field.match(/^(.*?)(Name|Key|Id)$/); + const base = match && match[1] ? match[1] : field; + const kind = match && match[1] ? match[2] : "Name"; + (dimensions[base] = dimensions[base] || {})[kind] = value; + } + const parts = Object.values(dimensions).map((d) => display(d.Name ?? d.Key ?? d.Id)); + return parts.length ? parts.join(" / ") : "Total"; +}; + +result = (data?.series || []).flatMap((bucket) => { + const time = new Date(bucket.time).toISOString(); + return Object.keys(bucket) + .filter((key) => key !== "time") + .map((key) => ({ + time, + group: labelFor(key), + value: Number(bucket[key]) || 0, + })); +}); diff --git a/plugins/LaunchDarkly/v1/dataStreams/scripts/approvalRequests.js b/plugins/LaunchDarkly/v1/dataStreams/scripts/approvalRequests.js new file mode 100644 index 00000000..fd8d6dfc --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/scripts/approvalRequests.js @@ -0,0 +1,50 @@ +// resourceId looks like proj/:env/:flag/. Only IDs are returned +// for the requestor (no name), so the member ID is surfaced as-is. +const iso = (ms) => (ms ? new Date(Number(ms)).toISOString() : null); + +const parseResource = (resourceId) => { + const parts = {}; + for (const segment of String(resourceId || "").split(":")) { + const slash = segment.indexOf("/"); + if (slash > 0) { + // Resource specifiers can carry tags after the key, e.g. "flag/my-flag;tag1,tag2". + parts[segment.slice(0, slash)] = segment.slice(slash + 1).split(";")[0]; + } + } + return parts; +}; + +const summariseInstructions = (instructions) => { + const kinds = (Array.isArray(instructions) ? instructions : []).map((i) => i?.kind).filter(Boolean); + return [...new Set(kinds)].join(", "); +}; + +const reviewStatusLabel = { approved: "Approved", declined: "Declined", pending: "Pending" }; + +result = (data?.items || []).map((req) => { + const resource = parseResource(req.resourceId); + const isFlag = resource.flag !== undefined; + const reviews = req.allReviews || []; + const instructions = summariseInstructions(req.instructions); + return { + approvalId: req._id, + // The description is optional, so fall back to what the request changes to keep a label. + description: req.description || instructions || resource.flag || null, + createdAt: iso(req.creationDate), + reviewState: reviewStatusLabel[req.reviewStatus] || req.reviewStatus, + reviewStatus: req.reviewStatus, + status: req.status, + requestorId: req.requestorId || null, + projectKey: resource.proj || null, + environmentKey: resource.env || null, + resourceKind: isFlag ? "flag" : resource.segment !== undefined ? "segment" : null, + flagId: isFlag && resource.proj ? `${resource.proj}/${resource.flag}` : null, + flagKey: isFlag ? resource.flag : null, + instructions, + executionDate: iso(req.executionDate), + appliedDate: iso(req.appliedDate), + reviewers: (req.notifyMemberIds || []).length, + approvals: reviews.filter((r) => r.kind === "approve").length, + declines: reviews.filter((r) => r.kind === "decline").length, + }; +}); diff --git a/plugins/LaunchDarkly/v1/dataStreams/scripts/auditLog.js b/plugins/LaunchDarkly/v1/dataStreams/scripts/auditLog.js new file mode 100644 index 00000000..4da66892 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/scripts/auditLog.js @@ -0,0 +1,69 @@ +// The audit log API returns title and description as markdown. Strip links, emphasis and +// escapes so the columns read as plain text, keeping the last link as the LaunchDarkly URL. +const plain = (text) => + (text || "") + .replace(/\[([^\]]*)\]\([^)]*\)/g, "$1") + .replace(/^\s*\*\s+/gm, "") + .replace(/\*\*|~~|`/g, "") + .replace(/\\(.)/g, "$1") + .split("\n") + .map((line) => line.trim()) + .filter(Boolean) + .join("; "); + +const lastLink = (title) => { + const links = [...(title || "").matchAll(/\]\((https?:[^)]+)\)/g)]; + return links.length ? links[links.length - 1][1] : null; +}; + +const memberName = (item) => { + const member = item.member; + if (member) { + return [member.firstName, member.lastName].filter(Boolean).join(" ") || member.email; + } + return item.token?.name || null; +}; + +// Resources look like proj/:env/:flag/; flag keys are only unique per project. +const parseResource = (resource) => { + const parts = {}; + for (const segment of (resource || "").split(":")) { + const slash = segment.indexOf("/"); + if (slash > 0) { + // Resource specifiers can carry tags after the key, e.g. "flag/my-flag;tag1,tag2". + parts[segment.slice(0, slash)] = segment.slice(slash + 1).split(";")[0]; + } + } + return parts; +}; + +// A change can touch several environments at once (e.g. creating a flag), so list them all. +const environments = (item) => + [...new Set((item.target?.resources || []).map((r) => parseResource(r).env).filter(Boolean))].join(", ") || + undefined; + +const iso = (ms) => (ms ? new Date(ms).toISOString() : null); + +// Paging walks back from now by passing the oldest entry's date as the next "before", so the +// first page has no upper bound; drop entries newer than the end of the selected timeframe. +const end = context.timeframe?.unixEnd ? context.timeframe.unixEnd * 1000 : Infinity; + +result = (data?.items || []).filter((item) => item.date <= end).map((item) => { + const resource = parseResource(item.target?.resources?.[0]); + const flagKey = resource.flag; + const project = resource.proj; + return { + id: item._id, + date: iso(item.date), + title: plain(item.title), + description: plain(item.shortDescription || item.description), + kind: item.kind, + action: item.titleVerb, + member: memberName(item), + project, + environment: environments(item), + flagId: flagKey && project ? `${project}/${flagKey}` : null, + flagKey, + link: lastLink(item.title), + }; +}); diff --git a/plugins/LaunchDarkly/v1/dataStreams/scripts/environments.js b/plugins/LaunchDarkly/v1/dataStreams/scripts/environments.js new file mode 100644 index 00000000..61273d17 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/scripts/environments.js @@ -0,0 +1,15 @@ +// Environment keys repeat across projects, so the object id combines both keys. +// Names such as "Production" repeat too, so the object name is prefixed with the project's name. +// SDK and mobile keys in the response are deliberately not copied into the rows. +const projectKey = context.objects[0].rawId; +const projectName = context.objects[0].name; + +result = (data?.items || []).map((env) => ({ + environmentId: `${projectKey}/${env.key}`, + name: `${projectName} / ${env.name}`, + environmentName: env.name, + environmentKey: env.key, + projectKey, + critical: env.critical === true, + tagList: (env.tags || []).join(", "), +})); diff --git a/plugins/LaunchDarkly/v1/dataStreams/scripts/flagEnvironments.js b/plugins/LaunchDarkly/v1/dataStreams/scripts/flagEnvironments.js new file mode 100644 index 00000000..6bf5d114 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/scripts/flagEnvironments.js @@ -0,0 +1,36 @@ +// The flag response holds an `environments` map keyed by environment key; one row per environment. +// No SDK or mobile keys are read, so none can leak into the rows. +const projectKey = context.objects[0].projectKey; +const variations = data?.variations || []; + +const variationLabel = (index) => { + if (index === undefined || index === null) { + return null; + } + const variation = variations[index]; + if (!variation) { + return `Variation ${index}`; + } + return variation.name || JSON.stringify(variation.value); +}; + +const sumTargetValues = (targets) => + (targets || []).reduce((total, target) => total + (target.values || []).length, 0); + +result = Object.entries(data?.environments || {}).map(([envKey, env]) => { + const fallthrough = env.fallthrough || {}; + return { + environmentId: `${projectKey}/${envKey}`, + environment: env._environmentName || envKey, + environmentKey: envKey, + on: env.on === true, + status: env.on === true ? "On" : "Off", + servingVariation: fallthrough.rollout ? "Percentage rollout" : variationLabel(fallthrough.variation), + offVariation: variationLabel(env.offVariation), + rules: (env.rules || []).length, + targets: sumTargetValues(env.targets) + sumTargetValues(env.contextTargets), + prerequisites: (env.prerequisites || []).length, + lastModified: env.lastModified ? new Date(env.lastModified).toISOString() : null, + version: env.version, + }; +}); diff --git a/plugins/LaunchDarkly/v1/dataStreams/scripts/flagEvaluations.js b/plugins/LaunchDarkly/v1/dataStreams/scripts/flagEvaluations.js new file mode 100644 index 00000000..33b4fa9d --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/scripts/flagEvaluations.js @@ -0,0 +1,25 @@ +// The API returns one wide row per time bucket: { time, "0": n, "1": n, ... }, where each numeric +// key is an index into metadata[] (one entry per variation, holding the variation value). Unpivot +// that into one row per (time, variation) so the data can be charted or grouped by variation. +const labelFor = (key) => { + const entry = (data?.metadata || [])[Number(key)]; + if (entry === undefined || entry === null || Number.isNaN(Number(key))) { + return key; + } + const raw = entry.name ?? entry.key; + if (raw === undefined || raw === null) { + return key; + } + return typeof raw === "string" ? raw : JSON.stringify(raw); +}; + +result = (data?.series || []).flatMap((bucket) => { + const time = new Date(bucket.time).toISOString(); + return Object.keys(bucket) + .filter((key) => key !== "time") + .map((key) => ({ + time, + variation: labelFor(key), + evaluations: Number(bucket[key]) || 0, + })); +}); diff --git a/plugins/LaunchDarkly/v1/dataStreams/scripts/flagList.js b/plugins/LaunchDarkly/v1/dataStreams/scripts/flagList.js new file mode 100644 index 00000000..24b45c3e --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/scripts/flagList.js @@ -0,0 +1,24 @@ +// Flag keys are only unique within a project, so the object id combines both keys. +// Rows are kept lean because large projects hold thousands of flags. +const projectKey = context.objects[0].rawId; + +const maintainerName = (flag) => { + const member = flag._maintainer; + if (member) { + return [member.firstName, member.lastName].filter(Boolean).join(" ") || member.email; + } + return flag._maintainerTeam?.name; +}; + +result = (data?.items || []).map((flag) => ({ + flagId: `${projectKey}/${flag.key}`, + name: flag.name, + flagKey: flag.key, + projectKey, + kind: flag.kind, + temporary: flag.temporary === true, + deprecated: flag.deprecated === true, + tagList: (flag.tags || []).join(", "), + maintainer: maintainerName(flag), + createdAt: flag.creationDate ? new Date(flag.creationDate).toISOString() : null, +})); diff --git a/plugins/LaunchDarkly/v1/dataStreams/scripts/flagStatuses.js b/plugins/LaunchDarkly/v1/dataStreams/scripts/flagStatuses.js new file mode 100644 index 00000000..be697984 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/scripts/flagStatuses.js @@ -0,0 +1,32 @@ +// The response has no flag key field; the parent link is "/api/v2/flags//". +// Flag keys are only unique within a project, so flagId combines both, matching the Flag objects. +const flagIdFromLink = (flag) => { + const href = flag?._links?.parent?.href; + return typeof href === "string" ? href.split("/").slice(-2).join("/") : null; +}; + +const scalar = (v) => (Array.isArray(v) ? v[0] : v); +const { projectKey, environmentKey, environmentName: environmentNameProperty } = context.objects[0]; +const project = scalar(projectKey); +const envKey = scalar(environmentKey); +const environmentName = scalar(environmentNameProperty) || envKey; + +result = (data?.items || []).map((flag) => { + const flagId = flagIdFromLink(flag); + const defaultValue = flag.default; + return { + flagId, + flagKey: flagId ? flagId.split("/").pop() : null, + environmentId: `${project}/${envKey}`, + environment: environmentName, + // Environment names such as "Production" repeat across projects, so cross-project views use this label. + environmentLabel: `${project} / ${environmentName}`, + project, + status: flag.name ? flag.name.charAt(0).toUpperCase() + flag.name.slice(1) : null, + health: flag.name || null, + lastRequested: flag.lastRequested || null, + default: defaultValue === undefined || defaultValue === null || typeof defaultValue === "string" + ? defaultValue ?? null + : JSON.stringify(defaultValue), + }; +}); diff --git a/plugins/LaunchDarkly/v1/dataStreams/scripts/flags.js b/plugins/LaunchDarkly/v1/dataStreams/scripts/flags.js new file mode 100644 index 00000000..8c57318c --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/scripts/flags.js @@ -0,0 +1,74 @@ +// Flag keys are only unique within a project, so the flag id combines both keys. +// With the env query arg, per-environment state sits under flag.environments.. +const { projectKey, environmentKey, environmentName: environmentNameProperty } = context.objects[0]; +const scalar = (v) => (Array.isArray(v) ? v[0] : v); +const project = scalar(projectKey); +const envKey = scalar(environmentKey); +const environmentName = scalar(environmentNameProperty); + +const maintainerName = (flag) => { + const member = flag._maintainer; + if (member) { + return [member.firstName, member.lastName].filter(Boolean).join(" ") || member.email; + } + return flag._maintainerTeam?.name; +}; + +const variationLabel = (flag, index) => { + const variation = flag.variations?.[index]; + if (!variation) { + return null; + } + return variation.name || JSON.stringify(variation.value); +}; + +const sumVariations = (summary, ...keys) => + Object.values(summary?.variations || {}).reduce( + (total, v) => total + keys.reduce((n, k) => n + (v?.[k] || 0), 0), + 0 + ); + +const servingVariation = (flag, env) => { + const entries = Object.entries(env._summary?.variations || {}); + if (entries.some(([, v]) => v?.rollout)) { + return "Percentage rollout"; + } + const fallthrough = entries.find(([, v]) => v?.isFallthrough); + return fallthrough ? variationLabel(flag, fallthrough[0]) : null; +}; + +const offVariation = (flag, env) => { + const entry = Object.entries(env._summary?.variations || {}).find(([, v]) => v?.isOff); + return entry ? variationLabel(flag, entry[0]) : null; +}; + +const iso = (ms) => (ms ? new Date(ms).toISOString() : null); + +result = (data?.items || []).map((flag) => { + const env = flag.environments?.[envKey] || {}; + const environment = env._environmentName || environmentName || envKey; + return { + flagId: `${project}/${flag.key}`, + name: flag.name, + flagKey: flag.key, + environmentId: `${project}/${envKey}`, + environment, + // Environment names such as "Production" repeat across projects, so cross-project views use this label. + environmentLabel: `${project} / ${environment}`, + project, + on: env.on === true, + status: env.on === true ? "On" : "Off", + servingVariation: servingVariation(flag, env), + offVariation: offVariation(flag, env), + rules: sumVariations(env._summary, "rules"), + targets: sumVariations(env._summary, "targets", "contextTargets"), + prerequisites: env._summary?.prerequisites || 0, + lastModified: iso(env.lastModified), + createdAt: iso(flag.creationDate), + kind: flag.kind, + temporary: flag.temporary === true, + tagList: (flag.tags || []).join(", "), + maintainer: maintainerName(flag), + version: env.version, + }; +}); diff --git a/plugins/LaunchDarkly/v1/dataStreams/scripts/scheduledChanges.js b/plugins/LaunchDarkly/v1/dataStreams/scripts/scheduledChanges.js new file mode 100644 index 00000000..aeee6dc3 --- /dev/null +++ b/plugins/LaunchDarkly/v1/dataStreams/scripts/scheduledChanges.js @@ -0,0 +1,35 @@ +// Instruction kinds are LaunchDarkly semantic-patch kinds; unknown kinds fall back to the raw kind. +// The environment key is a stream parameter, defaulting to "production" as in the endpoint path. +const envKey = context.config?.environmentKey || "production"; + +const KINDS = { + turnFlagOn: "Turn flag on", + turnFlagOff: "Turn flag off", + updateFallthroughVariationOrRollout: "Update fallthrough", + updateOffVariation: "Update off variation", + addRule: "Add rule", + removeRule: "Remove rule", + updateRuleVariationOrRollout: "Update rule serve", + addTargets: "Add targets", + removeTargets: "Remove targets", + addPrerequisite: "Add prerequisite", + removePrerequisite: "Remove prerequisite", + addClauses: "Add rule clauses", + removeClauses: "Remove rule clauses", + updateClause: "Update rule clause" +}; + +const iso = (ms) => (ms ? new Date(ms).toISOString() : null); + +const summarise = (instructions) => + (instructions || []).map((i) => KINDS[i?.kind] || i?.kind).filter(Boolean).join(", "); + +result = (data?.items || []).map((item) => ({ + id: item._id, + executionDate: iso(item.executionDate), + change: summarise(item.instructions), + environment: envKey, + createdAt: iso(item._creationDate), + creator: item._maintainerId, + conflicts: Array.isArray(item.conflicts) ? item.conflicts.length : 0 +})); diff --git a/plugins/LaunchDarkly/v1/defaultContent/environment.dash.json b/plugins/LaunchDarkly/v1/defaultContent/environment.dash.json new file mode 100644 index 00000000..b970a394 --- /dev/null +++ b/plugins/LaunchDarkly/v1/defaultContent/environment.dash.json @@ -0,0 +1,959 @@ +{ + "name": "Environment", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": [ + "{{variables.[LaunchDarkly Environment]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "e0bd3923-5b12-4a28-9f7a-ded57fccaf7e", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Flags On", + "description": "Flags switched on in this environment", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Environment]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flags]}}", + "name": "flags", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "on", + "operation": "equals", + "value": "true" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Environment]}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { + "type": "count" + }, + "comparisonColumn": "none", + "label": "On" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "d28186b7-5656-438a-bb7e-80f19d0e8f92", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Flags Off", + "description": "Flags switched off in this environment", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Environment]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flags]}}", + "name": "flags", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "on", + "operation": "equals", + "value": "false" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Environment]}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { + "type": "count" + }, + "comparisonColumn": "none", + "label": "Off" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "cf9b41a3-37e8-4786-9367-252b0ba2b4cd", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Inactive Flags", + "description": "Flags with no evaluations in this environment for the last 7 days", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Environment]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagStatuses]}}", + "name": "flagStatuses", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "status", + "operation": "equals", + "value": "Inactive" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Environment]}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { + "type": "count" + }, + "comparisonColumn": "none", + "label": "Inactive" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "0a770176-f22b-46e5-aa38-0d94803668a3", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Launched Flags", + "description": "Flags serving a single variation to everyone - ready to remove from code", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Environment]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagStatuses]}}", + "name": "flagStatuses", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "status", + "operation": "equals", + "value": "Launched" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Environment]}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { + "type": "count" + }, + "comparisonColumn": "none", + "label": "Launched" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "eaa459d5-040a-4a0b-bc8e-e9423d401752", + "x": 0, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Flag State", + "description": "Whether each flag is on in this environment, and the variation it is serving", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Environment]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flags]}}", + "name": "flags", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "name", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Environment]}}" + }, + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "name", + "stateColumn": "status", + "sublabel": "servingVariation", + "linkColumn": "name", + "columns": 3 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "statusGrid", + "encoding": { + "label": { + "field": "name", + "type": "nominal" + }, + "status": { + "field": "status", + "type": "nominal" + } + }, + "style": { + "statusGridSecondaryTemplate": "{{servingVariation}}", + "statusGridOrderByField": "name", + "statusGridOrderDirection": "asc", + "statusGridMaxBlockHeight": 80 + } + } + } + } + }, + { + "i": "2c81daf5-717f-4ae1-80ce-5fc44a142285", + "x": 2, + "y": 2, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Flag Lifecycle", + "description": "Flag lifecycle status in this environment", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Environment]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagStatuses]}}", + "name": "flagStatuses", + "pluginConfigId": "{{configId}}", + "group": { + "by": [ + [ + "status", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Environment]}}" + }, + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "status_uniqueValues", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "pie", + "encoding": { + "label": { + "field": "status_uniqueValues", + "type": "nominal", + "title": "Status" + }, + "value": { + "field": "count", + "type": "quantitative", + "aggregate": "sum" + } + }, + "style": { + "pieInnerRadius": 60, + "pieDonutCenterValue": "total", + "labelFontSize": 16, + "pieLegendStyle": "table", + "mappedColors": { + "New": "#9ca3af", + "Active": "#16a34a", + "Inactive": "#f59e0b", + "Launched": "#3b82f6" + } + } + } + } + } + }, + { + "i": "bd3d0ce9-b0f8-4b83-96f9-65ed191d3c46", + "x": 3, + "y": 2, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Properties", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Environment]}}" + ], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Environment]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Name", + "renderer": { + "type": "text" + } + }, + { + "field": "projectKey", + "title": "Project", + "renderer": { + "type": "text" + } + }, + { + "field": "environmentKey", + "title": "Key", + "renderer": { + "type": "text" + } + }, + { + "field": "critical", + "title": "Critical", + "renderer": { + "type": "boolean" + } + }, + { + "field": "tagList", + "title": "Tags", + "renderer": { + "type": "tag-list" + } + } + ] + }, + "style": { + "richTableTranspose": true + } + } + } + } + }, + { + "i": "5cea2885-0d75-44c7-9dcd-b9ffbf746d0a", + "x": 0, + "y": 6, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Cleanup Candidates", + "description": "Inactive and launched flags in this environment", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Environment]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagStatuses]}}", + "name": "flagStatuses", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "or", + "filters": [ + { + "column": "status", + "operation": "equals", + "value": "Inactive" + }, + { + "column": "status", + "operation": "equals", + "value": "Launched" + } + ] + }, + "sort": { + "by": [ + [ + "lastRequested", + "asc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Environment]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "flagName", + "health", + "status", + "lastRequested", + "default" + ], + "hiddenColumns": [ + "flagId", + "health[Expanded].rawState", + "flagKey" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "flagName", + "title": "Name", + "renderer": { + "type": "text" + } + }, + { + "field": "health", + "title": "Health", + "renderer": { + "type": "status", + "showLabel": false + }, + "align": "center" + }, + { + "field": "status", + "title": "Status", + "renderer": { + "type": "badge", + "colors": { + "Inactive": "#f59e0b", + "Launched": "#3b82f6" + } + } + }, + { + "field": "lastRequested", + "title": "Last Requested", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "default", + "title": "Default Value", + "renderer": { + "type": "text" + } + } + ] + }, + "style": { + "richTableSort": { + "field": "lastRequested", + "direction": "asc" + } + } + } + } + } + }, + { + "i": "e2b4910e-1f11-401e-a8b2-5b318d6d4992", + "x": 2, + "y": 6, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Recently Modified Flags", + "description": "Flags most recently changed in this environment", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Environment]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flags]}}", + "name": "flags", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "lastModified", + "desc" + ] + ], + "top": 10 + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Environment]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "status", + "servingVariation", + "lastModified", + "maintainer" + ], + "hiddenColumns": [ + "flagId", + "environmentId", + "status[Expanded].rawState", + "flagKey", + "environment", + "on", + "offVariation", + "rules", + "targets", + "prerequisites", + "createdAt", + "kind", + "temporary", + "tagList", + "version" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Name", + "renderer": { + "type": "text" + } + }, + { + "field": "status", + "title": "Status", + "renderer": { + "type": "status", + "showLabel": false + }, + "align": "center" + }, + { + "field": "servingVariation", + "title": "Serving", + "renderer": { + "type": "text" + } + }, + { + "field": "lastModified", + "title": "Last Modified", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "maintainer", + "title": "Maintainer", + "renderer": { + "type": "text" + } + } + ] + }, + "style": { + "richTableSort": { + "field": "lastModified", + "direction": "desc" + } + } + } + } + } + }, + { + "i": "a7951f62-3768-418c-a005-97e953964892", + "x": 0, + "y": 10, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Recent Changes", + "description": "Flag changes in this environment", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[auditLog]}}", + "name": "auditLog", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "environment": { + "variable": "{{variables.[LaunchDarkly Environment]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[LaunchDarkly Environments]}}" + } + } + }, + "variables": [ + "{{variables.[LaunchDarkly Environment]}}" + ], + "timeframe": "last7days", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "date", + "member", + "action", + "flagKey", + "environment", + "description", + "link" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "date", + "title": "When", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "member", + "title": "Who", + "renderer": { + "type": "text" + } + }, + { + "field": "action", + "title": "Action", + "renderer": { + "type": "text" + } + }, + { + "field": "flagKey", + "title": "Flag", + "renderer": { + "type": "text" + } + }, + { + "field": "environment", + "title": "Environment", + "renderer": { + "type": "tag-list", + "separator": "," + } + }, + { + "field": "description", + "title": "Change", + "renderer": { + "type": "text" + } + }, + { + "field": "link", + "title": "", + "renderer": { + "type": "link" + }, + "width": 60 + } + ] + }, + "style": { + "richTableSort": { + "field": "date", + "direction": "desc" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/LaunchDarkly/v1/defaultContent/flag.dash.json b/plugins/LaunchDarkly/v1/defaultContent/flag.dash.json new file mode 100644 index 00000000..bb0e7159 --- /dev/null +++ b/plugins/LaunchDarkly/v1/defaultContent/flag.dash.json @@ -0,0 +1,727 @@ +{ + "name": "Flag", + "schemaVersion": "1.5", + "timeframe": "last7days", + "variables": [ + "{{variables.[LaunchDarkly Flag]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "dcc2989e-514f-4fdb-91cd-ea0e9b440419", + "x": 0, + "y": 0, + "w": 3, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Environment State", + "description": "Whether this flag is on in each environment, and the variation it is serving", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Flag]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagEnvironments]}}", + "name": "flagEnvironments", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Flags]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Flag]}}" + }, + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "environment", + "stateColumn": "status", + "sublabel": "servingVariation", + "linkColumn": "environment", + "columns": 3 + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "statusGrid", + "encoding": { + "label": { + "field": "environment", + "type": "nominal" + }, + "status": { + "field": "status", + "type": "nominal" + } + }, + "style": { + "statusGridSecondaryTemplate": "{{servingVariation}}", + "statusGridOrderByField": "environment", + "statusGridOrderDirection": "asc" + } + } + } + } + }, + { + "i": "42207e05-4550-4fbb-9ed7-a84360bf8cdf", + "x": 3, + "y": 0, + "w": 1, + "h": 10, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Properties", + "description": "", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Flag]}}" + ], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Flags]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Flag]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Name", + "renderer": { + "type": "text" + } + }, + { + "field": "flagKey", + "title": "Key", + "renderer": { + "type": "text" + } + }, + { + "field": "projectKey", + "title": "Project", + "renderer": { + "type": "text" + } + }, + { + "field": "kind", + "title": "Kind", + "renderer": { + "type": "text" + } + }, + { + "field": "temporary", + "title": "Temporary", + "renderer": { + "type": "boolean" + } + }, + { + "field": "deprecated", + "title": "Deprecated", + "renderer": { + "type": "boolean" + } + }, + { + "field": "tagList", + "title": "Tags", + "renderer": { + "type": "tag-list" + } + }, + { + "field": "maintainer", + "title": "Maintainer", + "renderer": { + "type": "text" + } + }, + { + "field": "createdAt", + "title": "Created", + "renderer": { + "type": "date" + } + } + ] + }, + "style": { + "richTableTranspose": true, + "richTableWrapText": true + } + } + } + } + }, + { + "i": "7b3b59ad-aa6f-414c-9107-e4213dc7575d", + "x": 0, + "y": 2, + "w": 3, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Evaluations (Production)", + "description": "Evaluations of this flag in the environment with key `production`, by variation", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[LaunchDarkly Flag]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagEvaluations]}}", + "name": "flagEvaluations", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "environmentKey": "production" + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Flags]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Flag]}}" + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "evaluations" + ], + "seriesColumn": "variation", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "Evaluations", + "showYAxisLabel": true, + "showTrendLine": false + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Time" + }, + "y": { + "field": "evaluations", + "type": "quantitative", + "title": "Evaluations" + }, + "series": { + "field": "variation", + "type": "nominal", + "title": "Variation" + } + }, + "style": { + "cartesianAreaGradient": true, + "legendPosition": "bottom" + } + } + } + } + }, + { + "i": "3cdd9a89-f6ff-45f6-8b62-ce348eda98a4", + "x": 0, + "y": 6, + "w": 3, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Evaluations (Test)", + "description": "Evaluations of this flag in the environment with key `test`, by variation", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[LaunchDarkly Flag]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagEvaluations]}}", + "name": "flagEvaluations", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "environmentKey": "test" + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Flags]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Flag]}}" + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "evaluations" + ], + "seriesColumn": "variation", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "Evaluations", + "showYAxisLabel": true, + "showTrendLine": false + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Time" + }, + "y": { + "field": "evaluations", + "type": "quantitative", + "title": "Evaluations" + }, + "series": { + "field": "variation", + "type": "nominal", + "title": "Variation" + } + }, + "style": { + "cartesianAreaGradient": true, + "legendPosition": "bottom" + } + } + } + } + }, + { + "i": "a4fe0120-7689-4d63-a8a0-68f73cdc3dbc", + "x": 0, + "y": 10, + "w": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Targeting by Environment", + "description": "Targeting configuration of this flag in each environment", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[LaunchDarkly Flag]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagEnvironments]}}", + "name": "flagEnvironments", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Flags]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Flag]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "environment", + "status", + "servingVariation", + "offVariation", + "rules", + "targets", + "prerequisites", + "lastModified", + "version" + ], + "hiddenColumns": [ + "environmentId", + "environmentKey", + "on", + "status[Expanded].rawState" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "environment", + "title": "Environment", + "renderer": { + "type": "text" + } + }, + { + "field": "status", + "title": "Status", + "renderer": { + "type": "status", + "showLabel": false + }, + "align": "center" + }, + { + "field": "servingVariation", + "title": "Serving", + "renderer": { + "type": "text" + } + }, + { + "field": "offVariation", + "title": "Off Variation", + "renderer": { + "type": "text" + } + }, + { + "field": "rules", + "title": "Rules", + "renderer": { + "type": "number" + }, + "align": "right" + }, + { + "field": "targets", + "title": "Targets", + "renderer": { + "type": "number" + }, + "align": "right" + }, + { + "field": "prerequisites", + "title": "Prerequisites", + "renderer": { + "type": "number" + }, + "align": "right" + }, + { + "field": "lastModified", + "title": "Last Modified", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "version", + "title": "Version", + "renderer": { + "type": "number" + }, + "align": "right" + } + ] + }, + "style": { + "richTableSort": { + "field": "environment", + "direction": "asc" + } + } + } + } + } + }, + { + "i": "e3a8d229-8f59-4470-9774-2398d2da2b8c", + "x": 0, + "y": 13, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Change History", + "description": "Changes to this flag", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[auditLog]}}", + "name": "auditLog", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "flag": { + "variable": "{{variables.[LaunchDarkly Flag]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[LaunchDarkly Flags]}}" + } + } + }, + "variables": [ + "{{variables.[LaunchDarkly Flag]}}" + ], + "timeframe": "last30days", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "date", + "member", + "action", + "flagKey", + "environment", + "description", + "link" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "date", + "title": "When", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "member", + "title": "Who", + "renderer": { + "type": "text" + } + }, + { + "field": "action", + "title": "Action", + "renderer": { + "type": "text" + } + }, + { + "field": "flagKey", + "title": "Flag", + "renderer": { + "type": "text" + } + }, + { + "field": "environment", + "title": "Environment", + "renderer": { + "type": "tag-list", + "separator": "," + } + }, + { + "field": "description", + "title": "Change", + "renderer": { + "type": "text" + } + }, + { + "field": "link", + "title": "", + "renderer": { + "type": "link" + }, + "width": 60 + } + ] + }, + "style": { + "richTableSort": { + "field": "date", + "direction": "desc" + } + } + } + } + } + }, + { + "i": "73cab5f5-669d-461b-8993-57b207207c08", + "x": 0, + "y": 17, + "w": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Scheduled Changes (Production)", + "description": "Upcoming scheduled changes to this flag in the environment with key `production`", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[scheduledChanges]}}", + "name": "scheduledChanges", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "environmentKey": "production" + } + }, + "variables": [ + "{{variables.[LaunchDarkly Flag]}}" + ], + "scope": { + "scope": "{{scopes.[LaunchDarkly Flags]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Flag]}}" + }, + "timeframe": "none", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "executionDate", + "change", + "environment", + "createdAt", + "conflicts" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "executionDate", + "title": "Scheduled For", + "renderer": { + "type": "date" + } + }, + { + "field": "change", + "title": "Change", + "renderer": { + "type": "text" + } + }, + { + "field": "environment", + "title": "Environment", + "renderer": { + "type": "badge" + } + }, + { + "field": "createdAt", + "title": "Created", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "conflicts", + "title": "Conflicts", + "renderer": { + "type": "number", + "thresholds": [ + { + "to": 0, + "color": "#16a34a" + }, + { + "to": 1000, + "color": "#f59e0b" + } + ] + }, + "align": "right" + } + ] + }, + "style": { + "richTableSort": { + "field": "executionDate", + "direction": "asc" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/LaunchDarkly/v1/defaultContent/manifest.json b/plugins/LaunchDarkly/v1/defaultContent/manifest.json new file mode 100644 index 00000000..121a3961 --- /dev/null +++ b/plugins/LaunchDarkly/v1/defaultContent/manifest.json @@ -0,0 +1,24 @@ +{ + "items": [ + { + "name": "overview", + "type": "dashboard" + }, + { + "name": "usage", + "type": "dashboard" + }, + { + "name": "project", + "type": "dashboard" + }, + { + "name": "environment", + "type": "dashboard" + }, + { + "name": "flag", + "type": "dashboard" + } + ] +} diff --git a/plugins/LaunchDarkly/v1/defaultContent/overview.dash.json b/plugins/LaunchDarkly/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..2756249b --- /dev/null +++ b/plugins/LaunchDarkly/v1/defaultContent/overview.dash.json @@ -0,0 +1,937 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "b839abbe-7ffa-4d42-af08-ccf670218b1e", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Flags", + "description": "Feature flags imported across all projects", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Flags]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { + "type": "count" + }, + "comparisonColumn": "none" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "name", + "type": "nominal", + "aggregate": "count" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "6520a9cb-6540-4dc0-ae8a-61675cc72936", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Environments", + "description": "Environments imported across all projects", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { + "type": "count" + }, + "comparisonColumn": "none" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "name", + "type": "nominal", + "aggregate": "count" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "554771a0-5e3a-4c29-b372-71fe16cf3c1c", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Temporary Flags", + "description": "Flags marked as temporary, which should be removed once fully released", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "temporary", + "operation": "equals", + "value": "true" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Flags]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Temporary" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "01f1ea15-5166-497d-a27f-46b8a458462f", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Inactive Flags", + "description": "Flags with no evaluations in at least one environment for the last 7 days - candidates for cleanup", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[flagStatuses]}}", + "name": "flagStatuses", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "status", + "operation": "equals", + "value": "Inactive" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "distinctCount", + "names": [ + "flagId" + ] + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "flagId_distinctCount", + "comparisonColumn": "none", + "label": "Inactive" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "flagId_distinctCount", + "type": "quantitative" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "8061ce09-22eb-474e-af24-de2a8f238204", + "x": 0, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Flags On by Environment", + "description": "Number of flags switched on in each environment", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[flags]}}", + "name": "flags", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "on", + "operation": "equals", + "value": "true" + } + ] + }, + "group": { + "by": [ + [ + "environmentLabel", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + }, + "sort": { + "by": [ + [ + "count", + "desc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "environmentLabel_uniqueValues", + "yAxisData": [ + "count" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "Flags", + "showXAxisLabel": false, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": false, + "grouping": false, + "range": { + "type": "auto" + } + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "bar", + "encoding": { + "x": { + "field": "environmentLabel_uniqueValues", + "type": "nominal", + "title": "Environment" + }, + "y": { + "field": "count", + "type": "quantitative", + "title": "Flags on" + } + }, + "style": { + "cartesianBarBorderRadius": 4, + "showLabels": true + } + } + } + } + }, + { + "i": "d482e2e8-0460-495d-809f-b059e6eb3cea", + "x": 2, + "y": 2, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Flag Lifecycle", + "description": "Flag lifecycle status across every environment (one entry per flag per environment)", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[flagStatuses]}}", + "name": "flagStatuses", + "pluginConfigId": "{{configId}}", + "group": { + "by": [ + [ + "status", + "uniqueValues" + ] + ], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "status_uniqueValues", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "pie", + "encoding": { + "label": { + "field": "status_uniqueValues", + "type": "nominal", + "title": "Status" + }, + "value": { + "field": "count", + "type": "quantitative", + "aggregate": "sum" + } + }, + "style": { + "pieInnerRadius": 60, + "pieDonutCenterValue": "total", + "labelFontSize": 16, + "pieLegendStyle": "table", + "mappedColors": { + "New": "#9ca3af", + "Active": "#16a34a", + "Inactive": "#f59e0b", + "Launched": "#3b82f6" + } + } + } + } + } + }, + { + "i": "35d56329-f6f9-4583-a502-747ef9bc6bfb", + "x": 3, + "y": 2, + "w": 1, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Environments", + "description": "Environments across all projects - select one to see its flags", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "projectKey", + "environmentKey", + "critical" + ], + "hiddenColumns": [ + "id", + "link", + "links", + "type", + "label", + "sourceId", + "sourceType", + "rawId", + "tagList" + ], + "columnDisplayNames": { + "name": "Name", + "projectKey": "Project", + "environmentKey": "Key", + "critical": "Critical" + }, + "rowLinkColumnName": { + "value": "link", + "label": "Link" + } + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Name", + "renderer": { + "type": "text" + } + }, + { + "field": "projectKey", + "title": "Project", + "renderer": { + "type": "badge" + } + }, + { + "field": "environmentKey", + "title": "Key", + "renderer": { + "type": "text" + } + }, + { + "field": "critical", + "title": "Critical", + "renderer": { + "type": "boolean" + }, + "align": "center" + } + ] + }, + "style": { + "richTableSort": { + "field": "name", + "direction": "asc" + } + } + } + } + } + }, + { + "i": "7e830dcd-dc77-4017-a2f3-fd197858c4e5", + "x": 0, + "y": 6, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Recently Modified Flags", + "description": "Flags most recently changed in any environment", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[flags]}}", + "name": "flags", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "lastModified", + "desc" + ] + ], + "top": 15 + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Environments]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "environmentLabel", + "status", + "servingVariation", + "lastModified", + "kind", + "temporary", + "maintainer" + ], + "hiddenColumns": [ + "flagId", + "environmentId", + "status[Expanded].rawState", + "flagKey", + "on", + "offVariation", + "rules", + "targets", + "prerequisites", + "createdAt", + "tagList", + "version", + "environment", + "project" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Name", + "renderer": { + "type": "text" + } + }, + { + "field": "environmentLabel", + "title": "Environment", + "renderer": { + "type": "badge" + } + }, + { + "field": "status", + "title": "Status", + "renderer": { + "type": "status", + "showLabel": false + }, + "align": "center" + }, + { + "field": "servingVariation", + "title": "Serving", + "renderer": { + "type": "text" + } + }, + { + "field": "lastModified", + "title": "Last Modified", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "kind", + "title": "Kind", + "renderer": { + "type": "badge" + } + }, + { + "field": "temporary", + "title": "Temporary", + "renderer": { + "type": "boolean" + }, + "align": "center" + }, + { + "field": "maintainer", + "title": "Maintainer", + "renderer": { + "type": "text" + } + } + ] + }, + "style": { + "richTableSort": { + "field": "lastModified", + "direction": "desc" + } + } + } + } + } + }, + { + "i": "9c0e276a-0843-4b9e-a673-2f991e829e12", + "x": 0, + "y": 10, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Recent Changes", + "description": "Changes made in LaunchDarkly", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[auditLog]}}", + "name": "auditLog", + "pluginConfigId": "{{configId}}" + }, + "timeframe": "last7days", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "date", + "member", + "action", + "flagKey", + "environment", + "description", + "link" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "date", + "title": "When", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "member", + "title": "Who", + "renderer": { + "type": "text" + } + }, + { + "field": "action", + "title": "Action", + "renderer": { + "type": "text" + } + }, + { + "field": "flagKey", + "title": "Flag", + "renderer": { + "type": "text" + } + }, + { + "field": "environment", + "title": "Environment", + "renderer": { + "type": "tag-list", + "separator": "," + } + }, + { + "field": "description", + "title": "Change", + "renderer": { + "type": "text" + } + }, + { + "field": "link", + "title": "", + "renderer": { + "type": "link" + }, + "width": 60 + } + ] + }, + "style": { + "richTableSort": { + "field": "date", + "direction": "desc" + } + } + } + } + } + }, + { + "i": "77abb2fe-477d-4427-8267-9545048696cb", + "x": 0, + "y": 14, + "w": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Pending Approvals", + "description": "Approval requests waiting for review", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[approvalRequests]}}", + "name": "approvalRequests", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "reviewStatus": "pending" + } + }, + "timeframe": "none", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "description", + "reviewState", + "flagKey", + "environmentKey", + "instructions", + "createdAt", + "executionDate" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "description", + "title": "Request", + "renderer": { + "type": "text" + } + }, + { + "field": "reviewState", + "title": "Review", + "renderer": { + "type": "status" + } + }, + { + "field": "flagKey", + "title": "Flag", + "renderer": { + "type": "text" + } + }, + { + "field": "environmentKey", + "title": "Environment", + "renderer": { + "type": "badge" + } + }, + { + "field": "instructions", + "title": "Changes", + "renderer": { + "type": "text" + } + }, + { + "field": "createdAt", + "title": "Requested", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "executionDate", + "title": "Scheduled For", + "renderer": { + "type": "date" + } + } + ] + }, + "style": { + "richTableSort": { + "field": "createdAt", + "direction": "desc" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/LaunchDarkly/v1/defaultContent/project.dash.json b/plugins/LaunchDarkly/v1/defaultContent/project.dash.json new file mode 100644 index 00000000..5397b496 --- /dev/null +++ b/plugins/LaunchDarkly/v1/defaultContent/project.dash.json @@ -0,0 +1,711 @@ +{ + "name": "Project", + "schemaVersion": "1.5", + "timeframe": "last7days", + "variables": [ + "{{variables.[LaunchDarkly Project]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "5f0dec63-0b5e-4d59-9a41-b3ecace9e441", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Flags", + "description": "Flags in this project", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[LaunchDarkly Project]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagList]}}", + "name": "flagList", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Project]}}" + }, + "timeframe": "none", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "24962aac-7f9d-4570-a305-55aaa9d68364", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Temporary Flags", + "description": "Flags marked as temporary, which should be removed once released", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[LaunchDarkly Project]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagList]}}", + "name": "flagList", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "temporary", + "operation": "equals", + "value": "true" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Project]}}" + }, + "timeframe": "none", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "f12c13a7-da1d-4901-ab46-179e1cd37328", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Deprecated Flags", + "description": "Flags marked as deprecated", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[LaunchDarkly Project]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagList]}}", + "name": "flagList", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "deprecated", + "operation": "equals", + "value": "true" + } + ] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Project]}}" + }, + "timeframe": "none", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "c4df712b-7665-4d9a-865a-1719575256b8", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Environments", + "description": "Environments in this project", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[LaunchDarkly Project]}}" + ], + "dataStream": { + "id": "{{dataStreams.[environments]}}", + "name": "environments", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Project]}}" + }, + "timeframe": "none", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "scalar", + "encoding": { + "values": [ + { + "field": "count", + "type": "quantitative" + } + ] + }, + "style": { + "scalarLabelPosition": "none" + } + } + } + } + }, + { + "i": "f03a8e21-6d87-49ba-a852-99a420efc4ca", + "x": 0, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Project Environments", + "description": "Environments in this project", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[LaunchDarkly Project]}}" + ], + "dataStream": { + "id": "{{dataStreams.[environments]}}", + "name": "environments", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Project]}}" + }, + "timeframe": "none", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "environmentKey", + "critical", + "tagList" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Name", + "renderer": { + "type": "text" + } + }, + { + "field": "environmentKey", + "title": "Key", + "renderer": { + "type": "text" + } + }, + { + "field": "critical", + "title": "Critical", + "renderer": { + "type": "boolean" + }, + "align": "center" + }, + { + "field": "tagList", + "title": "Tags", + "renderer": { + "type": "tag-list" + } + } + ] + }, + "style": { + "richTableSort": { + "field": "name", + "direction": "asc" + } + } + } + } + } + }, + { + "i": "f1937839-a373-416b-b550-bd21825bb502", + "x": 2, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Service Connections by Environment", + "description": "Server-side SDK service connections in this project, month to date", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[accountUsage]}}", + "name": "accountUsage", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "metric": "service-connections", + "groupBy": "environmentId", + "project": { + "variable": "{{variables.[LaunchDarkly Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[LaunchDarkly Projects]}}" + } + } + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "group", + "showLegend": true, + "legendPosition": "bottom" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Date" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Value" + }, + "series": { + "field": "group", + "type": "nominal", + "title": "Group" + } + }, + "style": { + "cartesianAreaGradient": true, + "legendPosition": "bottom" + } + } + }, + "timeframe": "last30days", + "variables": [ + "{{variables.[LaunchDarkly Project]}}" + ] + } + }, + { + "i": "cb754c69-11a3-4a9d-8c0a-fc64a9864435", + "x": 0, + "y": 6, + "w": 4, + "h": 5, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Project Flags", + "description": "Flags in this project, newest first", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[LaunchDarkly Project]}}" + ], + "dataStream": { + "id": "{{dataStreams.[flagList]}}", + "name": "flagList", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[LaunchDarkly Projects]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[LaunchDarkly Project]}}" + }, + "timeframe": "none", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "flagKey", + "kind", + "temporary", + "deprecated", + "tagList", + "maintainer", + "createdAt" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "name", + "title": "Name", + "renderer": { + "type": "text" + } + }, + { + "field": "flagKey", + "title": "Key", + "renderer": { + "type": "text" + } + }, + { + "field": "kind", + "title": "Kind", + "renderer": { + "type": "badge" + } + }, + { + "field": "temporary", + "title": "Temporary", + "renderer": { + "type": "boolean" + }, + "align": "center" + }, + { + "field": "deprecated", + "title": "Deprecated", + "renderer": { + "type": "boolean" + }, + "align": "center" + }, + { + "field": "tagList", + "title": "Tags", + "renderer": { + "type": "tag-list" + } + }, + { + "field": "maintainer", + "title": "Maintainer", + "renderer": { + "type": "text" + } + }, + { + "field": "createdAt", + "title": "Created", + "renderer": { + "type": "date", + "relative": true + } + } + ] + }, + "style": { + "richTableSort": { + "field": "createdAt", + "direction": "desc" + } + } + } + } + } + }, + { + "i": "a6cb3517-bda7-4829-8366-d690c1638b18", + "x": 0, + "y": 11, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Recent Changes", + "description": "Changes to flags in this project", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[auditLog]}}", + "name": "auditLog", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "project": { + "variable": "{{variables.[LaunchDarkly Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[LaunchDarkly Projects]}}" + } + } + }, + "timeframe": "last7days", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "date", + "member", + "action", + "flagKey", + "environment", + "description", + "link" + ] + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "richTable", + "encoding": { + "columns": [ + { + "field": "date", + "title": "When", + "renderer": { + "type": "date", + "relative": true + } + }, + { + "field": "member", + "title": "Who", + "renderer": { + "type": "text" + } + }, + { + "field": "action", + "title": "Action", + "renderer": { + "type": "text" + } + }, + { + "field": "flagKey", + "title": "Flag", + "renderer": { + "type": "text" + } + }, + { + "field": "environment", + "title": "Environment", + "renderer": { + "type": "tag-list", + "separator": "," + } + }, + { + "field": "description", + "title": "Change", + "renderer": { + "type": "text" + } + }, + { + "field": "link", + "title": "", + "renderer": { + "type": "link" + }, + "width": 60 + } + ] + }, + "style": { + "richTableSort": { + "field": "date", + "direction": "desc" + } + } + } + }, + "variables": [ + "{{variables.[LaunchDarkly Project]}}" + ] + } + } + ] + } +} diff --git a/plugins/LaunchDarkly/v1/defaultContent/scopes.json b/plugins/LaunchDarkly/v1/defaultContent/scopes.json new file mode 100644 index 00000000..1989b724 --- /dev/null +++ b/plugins/LaunchDarkly/v1/defaultContent/scopes.json @@ -0,0 +1,53 @@ +[ + { + "name": "LaunchDarkly Projects", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Project" + ] + } + }, + "variable": { + "name": "LaunchDarkly Project", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "LaunchDarkly Environments", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Environment" + ] + } + }, + "variable": { + "name": "LaunchDarkly Environment", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "LaunchDarkly Flags", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Flag" + ] + } + }, + "variable": { + "name": "LaunchDarkly Flag", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/LaunchDarkly/v1/defaultContent/usage.dash.json b/plugins/LaunchDarkly/v1/defaultContent/usage.dash.json new file mode 100644 index 00000000..c93eeccd --- /dev/null +++ b/plugins/LaunchDarkly/v1/defaultContent/usage.dash.json @@ -0,0 +1,282 @@ +{ + "name": "Usage", + "schemaVersion": "1.5", + "timeframe": "last30days", + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "79ca502a-005b-4a78-9210-980cb974fd7f", + "x": 0, + "y": 0, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Monthly Active Users", + "description": "Monthly active users, month to date", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[accountUsage]}}", + "name": "accountUsage", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "metric": "total-mau" + } + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "group", + "showLegend": true, + "legendPosition": "bottom" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Date" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Value" + }, + "series": { + "field": "group", + "type": "nominal", + "title": "Group" + } + }, + "style": { + "cartesianAreaGradient": true, + "legendPosition": "bottom" + } + } + } + } + }, + { + "i": "52d1db11-ca74-407f-98ca-451b4be5f094", + "x": 2, + "y": 0, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Contexts", + "description": "Contexts evaluated, month to date", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[accountUsage]}}", + "name": "accountUsage", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "metric": "total-contexts" + } + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "group", + "showLegend": true, + "legendPosition": "bottom" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Date" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Value" + }, + "series": { + "field": "group", + "type": "nominal", + "title": "Group" + } + }, + "style": { + "cartesianAreaGradient": true, + "legendPosition": "bottom" + } + } + } + } + }, + { + "i": "8d1ea62d-ce93-4ee9-9634-51aebb0f2988", + "x": 0, + "y": 4, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Service Connections by SDK", + "description": "Server-side SDK service connections, month to date", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[accountUsage]}}", + "name": "accountUsage", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "metric": "service-connections", + "groupBy": "sdkName" + } + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "group", + "showLegend": true, + "legendPosition": "bottom" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Date" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Value" + }, + "series": { + "field": "group", + "type": "nominal", + "title": "Group" + } + }, + "style": { + "cartesianAreaGradient": true, + "legendPosition": "bottom" + } + } + } + } + }, + { + "i": "6bcf4b06-231d-42fd-97a0-958f2ac9b2e6", + "x": 2, + "y": 4, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Service Connections by Environment", + "description": "Server-side SDK service connections, month to date", + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[accountUsage]}}", + "name": "accountUsage", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "metric": "service-connections", + "groupBy": "environmentId" + } + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "time", + "yAxisColumn": [ + "value" + ], + "seriesColumn": "group", + "showLegend": true, + "legendPosition": "bottom" + } + } + }, + "vizSpec": { + "version": "1.0", + "visualization": { + "chartType": "line", + "encoding": { + "x": { + "field": "time", + "type": "temporal", + "title": "Date" + }, + "y": { + "field": "value", + "type": "quantitative", + "title": "Value" + }, + "series": { + "field": "group", + "type": "nominal", + "title": "Group" + } + }, + "style": { + "cartesianAreaGradient": true, + "legendPosition": "bottom" + } + } + } + } + } + ] + } +} diff --git a/plugins/LaunchDarkly/v1/docs/README.md b/plugins/LaunchDarkly/v1/docs/README.md new file mode 100644 index 00000000..32c46e91 --- /dev/null +++ b/plugins/LaunchDarkly/v1/docs/README.md @@ -0,0 +1,68 @@ +Monitor your [LaunchDarkly](https://launchdarkly.com) feature flags in SquaredUp — flag state in each environment, flag lifecycle status, and evaluation volume over time — via the [LaunchDarkly REST API](https://apidocs.launchdarkly.com). + +## Setup + +You will need a LaunchDarkly **access token**. The plugin only reads data, so the built-in **Reader** role is enough. + +1. Sign in to LaunchDarkly at [app.launchdarkly.com](https://app.launchdarkly.com) (Commercial), [app.eu.launchdarkly.com](https://app.eu.launchdarkly.com) (EU) or [app.launchdarkly.us](https://app.launchdarkly.us) (Federal). +2. Go to **Organization settings → Authorization** (`/settings/authorization` on your instance) and, under **Access tokens**, click **Create token**. +3. Give the token a name (for example `SquaredUp`), choose the **Reader** role, and click **Save token**. +4. Copy the token straight away — LaunchDarkly only shows it once — and paste it into the **Access token** field. +5. Choose the **Instance** you sign in to. Most accounts use **Commercial**; choose **EU** if you sign in at `app.eu.launchdarkly.com`, or **Federal** if you sign in at `app.launchdarkly.us`. + +See [Creating API access tokens](https://launchdarkly.com/docs/home/account/api-create) in the LaunchDarkly documentation for more detail. + +## Configuration fields + +| Field | What it is | Where to find it | Required | +| ---------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------- | -------- | +| **Instance** | The LaunchDarkly instance your account is hosted on. | The address you sign in at. | Yes | +| **Access token** | A personal or service access token used to authenticate requests. | LaunchDarkly → **Organization settings → Authorization** (see above). | Yes | + +On save, the plugin checks the token by looking up the identity it belongs to. If the token is invalid or was created on a different instance, setup fails with an authentication error. + +## What this plugin monitors + +- **Flag state per environment** — whether each flag is on or off, which variation it serves, and how many targeting rules, individual targets and prerequisites it has. +- **Flag lifecycle** — LaunchDarkly's flag status in each environment: **New**, **Active**, **Inactive** (not evaluated for 7 days, a cleanup candidate) or **Launched** (serving one variation for 7 days, ready to remove from code), with when the flag was last requested. +- **Flag evaluations** — how often a flag was evaluated over time, broken down by variation. +- **Flag inventory** — every project, environment and flag, with kind, temporary/permanent, tags, maintainer and creation date. +- **Changes** — who changed which flag and when, from the LaunchDarkly audit log, plus approval requests and scheduled changes. +- **Account usage** — monthly active users, contexts and SDK service connections over time. + +The out-of-the-box dashboards include an account-wide **Overview** and **Usage** dashboard, plus perspectives for each **Project**, **Environment** and **Flag**. + +## Data streams + +- **Environments** — the environments in a project, with key, critical setting and tags. Scoped to one or more projects. +- **Flag List** — every flag in a project with kind, temporary/deprecated, tags, maintainer and creation date. Scoped to one or more projects. +- **Flags** — every flag in an environment with its on/off state, serving variation, targeting counts and flag details. Scoped to one or more environments. +- **Flag Statuses** — the lifecycle status (new, active, inactive, launched) and last requested time of every flag in an environment. Scoped to one or more environments. +- **Flag Environments** — the state of one flag in each environment of its project. Scoped to a flag. +- **Flag Evaluations** — evaluation counts over time for a flag, by variation, in the environment named by the **Environment key** parameter (default `production`). Scoped to a flag. +- **Audit Log** — changes made in LaunchDarkly, with who made them, optionally filtered to a project, flag or environment. Account-wide. +- **Approval Requests** — approval requests for flag changes and their review status, optionally only pending ones. Account-wide. +- **Scheduled Changes** — upcoming scheduled changes to a flag in the environment named by the **Environment key** parameter (default `production`). Scoped to a flag. +- **Account Usage** — monthly active users, contexts or service connections per day, optionally grouped by project, environment or SDK and filtered to a project. Account-wide. + +## What gets indexed + +| Object type | API source | Represents | +| --------------- | ------------------------------------------------- | ------------------------------------------------------ | +| **Project** | `GET /api/v2/projects` | A LaunchDarkly project. | +| **Environment** | `GET /api/v2/projects/{projectKey}/environments` | An environment, such as Production, within a project. | +| **Flag** | `GET /api/v2/flags/{projectKey}` | A feature flag (archived flags are not imported). | + +**Relationships:** each Environment and Flag is related to the Project it belongs to, so a project's environments and flags appear when you open it. SDK keys and mobile keys are never imported. + +## Known limitations + +- **Flag evaluations use a beta API** — LaunchDarkly may change it without notice. Granularity is chosen by LaunchDarkly from the timeframe: per minute under 2 hours, hourly under 2 days, daily beyond that. Per-minute data is retained for 70 days. +- **Evaluations are per flag and per environment** — there is no account-wide evaluation total, and the **Environment key** parameter must match an environment key in the flag's project (not its display name). The out-of-the-box **Flag** dashboard charts the `production` and `test` environments that LaunchDarkly creates in every new project; copy a tile and change its **Environment key** to chart any other environment. +- **Audit log volume** — LaunchDarkly returns at most 20 audit log entries per request, so busy accounts need many requests for long timeframes. Filtering by project or environment only matches flag changes. +- **Approval requests and scheduled changes** — LaunchDarkly only returns member IDs for requesters, reviewers and creators, not names. Approvals need to be enabled for an environment (generally an Enterprise feature), and the approvals API allows only a few requests per 10 seconds, so avoid adding it to many tiles. +- **Account usage uses a beta API** — values are LaunchDarkly's month-to-date totals per day, which reset at the start of each month. The timeframe starts at 7 days, as the data is daily. Availability can depend on your LaunchDarkly plan. +- **Large accounts** — LaunchDarkly returns at most 100 flags per request, so very large accounts (roughly 20,000 or more flags across all projects) may exceed SquaredUp's import limits. +- **Rate limits** — LaunchDarkly rate-limits API calls per account and per route. Dashboards that query many environments or flags at once share that allowance with your other API integrations. +- **Archived flags** are not imported or shown. +- **Read-only** — the plugin never creates, modifies or deletes anything in LaunchDarkly. diff --git a/plugins/LaunchDarkly/v1/icon.svg b/plugins/LaunchDarkly/v1/icon.svg new file mode 100644 index 00000000..932611ff --- /dev/null +++ b/plugins/LaunchDarkly/v1/icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugins/LaunchDarkly/v1/indexDefinitions/default.json b/plugins/LaunchDarkly/v1/indexDefinitions/default.json new file mode 100644 index 00000000..0953c4c5 --- /dev/null +++ b/plugins/LaunchDarkly/v1/indexDefinitions/default.json @@ -0,0 +1,53 @@ +{ + "steps": [ + { + "name": "projects", + "dataStream": { "name": "projects" }, + "timeframe": "none", + "objectMapping": { + "id": "key", + "name": "name", + "type": { "value": "Project" } + } + }, + { + "name": "environments", + "dataStream": { "name": "environments" }, + "scope": { + "query": "g.V().has(\"sourceType\", \"Project\")" + }, + "timeframe": "none", + "objectMapping": { + "id": "environmentId", + "name": "name", + "type": { "value": "Environment" }, + "properties": ["environmentKey", "environmentName", "projectKey", "critical", "tagList"] + }, + "dependsOn": ["projects"] + }, + { + "name": "flags", + "dataStream": { "name": "flagList" }, + "scope": { + "query": "g.V().has(\"sourceType\", \"Project\")" + }, + "timeframe": "none", + "objectMapping": { + "id": "flagId", + "name": "name", + "type": { "value": "Flag" }, + "properties": [ + "flagKey", + "projectKey", + "kind", + "temporary", + "deprecated", + "tagList", + "maintainer", + "createdAt" + ] + }, + "dependsOn": ["projects"] + } + ] +} diff --git a/plugins/LaunchDarkly/v1/metadata.json b/plugins/LaunchDarkly/v1/metadata.json new file mode 100644 index 00000000..204bd455 --- /dev/null +++ b/plugins/LaunchDarkly/v1/metadata.json @@ -0,0 +1,52 @@ +{ + "name": "launchdarkly", + "displayName": "LaunchDarkly", + "version": "1.0.0", + "author": { "name": "@clarkd", "type": "community" }, + "description": "Monitor LaunchDarkly feature flags across your projects and environments, including flag state, lifecycle status and evaluation volume.", + "category": "CI/CD Tools", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, + "restrictedToPlatforms": [], + "keywords": [ + "launchdarkly", + "feature flags", + "feature management", + "flags", + "releases", + "toggles" + ], + "objectTypes": ["Project", "Environment", "Flag"], + "links": [ + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/LaunchDarkly/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/LaunchDarkly", + "label": "Repository" + } + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "baseUrl": "{{ ({ eu: 'https://app.eu.launchdarkly.com', federal: 'https://app.launchdarkly.us' })[instance] || 'https://app.launchdarkly.com' }}/api/v2", + "authMode": "none", + "headers": [ + { "key": "Authorization", "value": "{{accessToken}}" }, + { "key": "LD-API-Version", "value": "20240415" } + ] + } + }, + "screenshots": [ + "https://github.com/squaredup/plugins/blob/main/plugins/LaunchDarkly/v1/screenshots/overview.png", + "https://github.com/squaredup/plugins/blob/main/plugins/LaunchDarkly/v1/screenshots/project.png", + "https://github.com/squaredup/plugins/blob/main/plugins/LaunchDarkly/v1/screenshots/environment.png", + "https://github.com/squaredup/plugins/blob/main/plugins/LaunchDarkly/v1/screenshots/flag.png", + "https://github.com/squaredup/plugins/blob/main/plugins/LaunchDarkly/v1/screenshots/usage.png" + ] +} diff --git a/plugins/LaunchDarkly/v1/screenshots/environment.png b/plugins/LaunchDarkly/v1/screenshots/environment.png new file mode 100644 index 00000000..b52448c0 Binary files /dev/null and b/plugins/LaunchDarkly/v1/screenshots/environment.png differ diff --git a/plugins/LaunchDarkly/v1/screenshots/flag.png b/plugins/LaunchDarkly/v1/screenshots/flag.png new file mode 100644 index 00000000..58aa1bcc Binary files /dev/null and b/plugins/LaunchDarkly/v1/screenshots/flag.png differ diff --git a/plugins/LaunchDarkly/v1/screenshots/overview.png b/plugins/LaunchDarkly/v1/screenshots/overview.png new file mode 100644 index 00000000..84753956 Binary files /dev/null and b/plugins/LaunchDarkly/v1/screenshots/overview.png differ diff --git a/plugins/LaunchDarkly/v1/screenshots/project.png b/plugins/LaunchDarkly/v1/screenshots/project.png new file mode 100644 index 00000000..b9c2a0a3 Binary files /dev/null and b/plugins/LaunchDarkly/v1/screenshots/project.png differ diff --git a/plugins/LaunchDarkly/v1/screenshots/usage.png b/plugins/LaunchDarkly/v1/screenshots/usage.png new file mode 100644 index 00000000..2f92aa44 Binary files /dev/null and b/plugins/LaunchDarkly/v1/screenshots/usage.png differ diff --git a/plugins/LaunchDarkly/v1/ui.json b/plugins/LaunchDarkly/v1/ui.json new file mode 100644 index 00000000..81329ae8 --- /dev/null +++ b/plugins/LaunchDarkly/v1/ui.json @@ -0,0 +1,23 @@ +[ + { + "type": "radio", + "name": "instance", + "label": "Instance", + "help": "Choose the instance you sign in to. Most accounts use Commercial.", + "defaultValue": "commercial", + "options": [ + { "value": "commercial", "label": "Commercial (app.launchdarkly.com)" }, + { "value": "eu", "label": "EU (app.eu.launchdarkly.com)" }, + { "value": "federal", "label": "Federal (app.launchdarkly.us)" } + ], + "validation": { "required": true } + }, + { + "type": "password", + "name": "accessToken", + "label": "Access token", + "help": "Create a token with the **Reader** role under **Organization settings → Authorization** (`/settings/authorization` on your instance).", + "placeholder": "api-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "validation": { "required": true } + } +]