Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 23 additions & 27 deletions packages/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contribution Guide

This project hosts customer-specific elements like dashboards and events for IBM Instana monitoring, bundled into integration packages.
This project hosts customer-specific elements like dashboards, events, entities, and smart alerts for IBM Instana monitoring, bundled into integration packages.

## Steps to Contribute

Expand All @@ -11,47 +11,43 @@ Clone this repository and areate a dev branch based on `main`.
git clone https://github.com/instana/observability-as-code.git
```

### 2. **Create a new package:**
Under `packages/`, create a new directory (e.g. `@instana-integration/packagename`). It should contain:
### 2. **Initialize a new package:**
Use the `init` command to create a new package structure under `packages/@instana-integration/packagename`. The CLI will generate the `package.json` and `README.md` files and create directories for the integration element types you select.

Supported integration element types:
- **Dashboards**: Custom monitoring dashboards
- **Events**: Custom event definitions
- **Entities**: Custom entity definitions
- **Smart Alerts**: Intelligent metric-based alerts

Example package structure after initialization:

```shell
packages/@instana-integration/packagename/
├── dashboards/
│ └── my-dashboard.json
├── events/
│ └── my-event.json
└── package.json
├── dashboards/ # (optional) Custom dashboards
├── events/ # (optional) Event definitions
├── entities/ # (optional) Entity definitions
├── smart-alerts/ # (optional) Smart alert definitions
├── README.md # Generated by init command
└── package.json # Generated by init command
```

### 3. Build custom elements:
### 3. Build custom elements:

* Define custom elements in the Instana UI
* Export custom elements from Instana UI to your local
* Export custom elements from Instana UI to your local package using the CLI
* Set access rules to GLOBAL in dashboards for public sharing

### 4. Create package.json:
Create `package.json` file by using the `init` command provided by the Instana CLI for Integration Package Management and include required fields.

```shell
{
"name": "@instana-integration/package-name",
"version": "1.0.0",
"description": "Custom monitoring for XYZ systems.",
"author": "author",
"license": "MIT"
}
```

### 5. Create a Pull Request:
### 4. Create a Pull Request:
- Commit and push your changes
- Open a PR against the `main` branch

### 6. Automated Publishing:
### 5. Automated Publishing:
After your PR is reviewed and merged into main, the GitHub workflow will automatically publish your package to [Instana integration organization](https://www.npmjs.com/org/instana-integration).


## 📚 Learn More

- [Blog: Making Your Instana Dashboards Publicly Sharable](https://community.ibm.com/community/user/blogs/ying-mo2/2025/02/22/making-your-instana-dashboards-publicly-sharable)
- [Blog: Contributing to IBM Instana Observability as Code GitHub Repository](https://community.ibm.com/community/user/blogs/ying-mo2/2025/03/09/contributing-to-ibm-instana-observability-as-code)

- [Blog: Event Definitions in Instana: From Manual Setup to Reusable Packages](https://community.ibm.com/community/user/blogs/disha-bhagat/2025/08/20/sharing-and-reusing-instana-event-definitions)
- [Blog: Scale Your Alerting: Package and Share Instana Smart Alerts](https://community.ibm.com/community/user/blogs/disha-bhagat/2026/02/27/scale-your-alerting-package-and-share-smart-alerts)
5 changes: 5 additions & 0 deletions tools/integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

The Instana CLI for Integration Package Management is used to manage the lifecycle of Instana integration package. For example, you can use this CLI to download the integration package from public website to your local machine, then install the package into an existing Instana environment.

## Requirements

- **For binary users**: No Node.js installation required. The binary includes an embedded Node.js 18 runtime.
- **For developers**: Node.js 18 or higher is required when running from source code.

## For end users

### Download the CLI
Expand Down
52 changes: 40 additions & 12 deletions tools/integration/src/__tests__/handlers/export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,11 +592,15 @@ describe('handleExport', () => {
mockedUtils.sanitizeFileName = jest.fn().mockReturnValue('smart-alert-1');
mockFilterElementsBy.mockImplementation((items) => items);

// Mock the 3 smart-alert endpoints returning data from first endpoint
// Mock the 7 smart-alert endpoints returning data from first endpoint
mockAxiosInstance.get
.mockResolvedValueOnce({ status: 200, data: mockSmartAlerts }) // mobile-app endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // application endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // infra endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // website endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // synthetics endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // service-levels endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // logs endpoint
.mockResolvedValueOnce({ status: 200, data: { id: 'alert-1', name: 'Smart Alert 1' } }); // fetch single alert

await handleExport(argv);
Expand Down Expand Up @@ -630,10 +634,14 @@ describe('handleExport', () => {
mockedUtils.sanitizeFileName = jest.fn().mockReturnValue('smart-alert-1');

mockAxiosInstance.get
.mockResolvedValueOnce({ status: 200, data: mockSmartAlerts })
.mockResolvedValueOnce({ status: 200, data: [] })
.mockResolvedValueOnce({ status: 200, data: [] })
.mockResolvedValueOnce({ status: 200, data: { id: 'alert-1', name: 'Smart Alert 1' } });
.mockResolvedValueOnce({ status: 200, data: mockSmartAlerts }) // mobile-app endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // application endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // infra endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // website endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // synthetics endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // service-levels endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // logs endpoint
.mockResolvedValueOnce({ status: 200, data: { id: 'alert-1', name: 'Smart Alert 1' } }); // fetch single alert

await handleExport(argv);

Expand Down Expand Up @@ -705,6 +713,10 @@ describe('handleExport', () => {
.mockResolvedValueOnce({ status: 200, data: mobileAlerts }) // mobile-app endpoint
.mockResolvedValueOnce({ status: 200, data: appAlerts }) // application endpoint
.mockResolvedValueOnce({ status: 200, data: infraAlerts }) // infra endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // website endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // synthetics endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // service-levels endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // logs endpoint
.mockResolvedValueOnce({ status: 200, data: { id: 'alert-1', name: 'Mobile Alert' } })
.mockResolvedValueOnce({ status: 200, data: { id: 'alert-2', name: 'App Alert' } })
.mockResolvedValueOnce({ status: 200, data: { id: 'alert-3', name: 'Infra Alert' } });
Expand Down Expand Up @@ -741,10 +753,14 @@ describe('handleExport', () => {
);

mockAxiosInstance.get
.mockResolvedValueOnce({ status: 200, data: mockSmartAlerts })
.mockResolvedValueOnce({ status: 200, data: [] })
.mockResolvedValueOnce({ status: 200, data: [] })
.mockResolvedValueOnce({ status: 200, data: { id: 'alert-1', name: 'Critical Alert' } });
.mockResolvedValueOnce({ status: 200, data: mockSmartAlerts }) // mobile-app endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // application endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // infra endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // website endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // synthetics endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // service-levels endpoint
.mockResolvedValueOnce({ status: 200, data: [] }) // logs endpoint
.mockResolvedValueOnce({ status: 200, data: { id: 'alert-1', name: 'Critical Alert' } }); // fetch single alert

await handleExport(argv);

Expand Down Expand Up @@ -815,6 +831,10 @@ describe('handleExport', () => {
.mockResolvedValueOnce({ status: 200, data: mobileAlerts }) // mobile-app endpoint succeeds
.mockRejectedValueOnce(new Error('API Error')) // application endpoint fails
.mockRejectedValueOnce(new Error('API Error')) // infra endpoint fails
.mockRejectedValueOnce(new Error('API Error')) // website endpoint fails
.mockRejectedValueOnce(new Error('API Error')) // synthetics endpoint fails
.mockRejectedValueOnce(new Error('API Error')) // service-levels endpoint fails
.mockRejectedValueOnce(new Error('API Error')) // logs endpoint fails
.mockResolvedValueOnce({ status: 200, data: { id: 'alert-1', name: 'Mobile Alert' } }); // fetch single alert

await handleExport(argv);
Expand Down Expand Up @@ -852,7 +872,15 @@ describe('handleExport', () => {
.mockResolvedValueOnce({ status: 200, data: mockSmartAlerts }) // mobile-app list
.mockResolvedValueOnce({ status: 200, data: [] }) // application list
.mockResolvedValueOnce({ status: 200, data: [] }) // infra list
// First two endpoints fail, third succeeds
.mockResolvedValueOnce({ status: 200, data: [] }) // website list
.mockResolvedValueOnce({ status: 200, data: [] }) // synthetics list
.mockResolvedValueOnce({ status: 200, data: [] }) // service-levels list
.mockResolvedValueOnce({ status: 200, data: [] }) // logs list
// First six endpoints fail, seventh succeeds
.mockRejectedValueOnce(new Error('Not found'))
.mockRejectedValueOnce(new Error('Not found'))
.mockRejectedValueOnce(new Error('Not found'))
.mockRejectedValueOnce(new Error('Not found'))
.mockRejectedValueOnce(new Error('Not found'))
.mockRejectedValueOnce(new Error('Not found'))
.mockResolvedValueOnce({ status: 200, data: { id: 'alert-1', name: 'Smart Alert 1' } });
Expand Down Expand Up @@ -886,8 +914,8 @@ describe('handleExport', () => {

await handleExport(argv);

// Should call get for dashboards, events, entities, and smart-alerts (3 endpoints for smart-alerts)
expect(mockAxiosInstance.get).toHaveBeenCalledTimes(6);
// Should call get for dashboards, events, entities, and smart-alerts (7 endpoints for smart-alerts)
expect(mockAxiosInstance.get).toHaveBeenCalledTimes(10);
expect(mockedLogger.info).toHaveBeenCalledWith('Total dashboard(s) processed: 0');
expect(mockedLogger.info).toHaveBeenCalledWith('Total event(s) processed: 0');
expect(mockedLogger.info).toHaveBeenCalledWith('Total entities processed: 0');
Expand Down
Loading