查找

Artículo
· 3 mar, 2024 Lectura de 5 min

How to send messages to Microsoft Teams

Hi community,

The aim of this article is to explain how to create messaging between IRIS and Microsoft Teams.

In my company, we wanted to monitor error messages, and we used the Ens.Alert class to redirect those error messages through a Business Operation that sent an email.
The problem was that we sent those error messages to a support account where there were many emails. We wanted something specific for a specific team.

So we investigated how to make these messages reach the development team directly and they could have, in real time, a notification of an error in our production.
In our company we use Microsoft Teams as a corporate tool, so we asked ourselves: How could we make these messages reach the IRIS development team?

Previous steps

Please, expand to know how to configure your teams with the app Incoming Webhook.

 
Previous steps

Note: Webhook link is divided in two parts. Server and URL, remember this when you going to configure the component.

https://YOURCOMPANY.webhook.office.com/webhookb2/40cc6704-1bc5-4f87-xxxx-xxxxxxxxf@5xxxxxa-643b-47a3-xxxxx-fc962cc7cdb2/IncomingWebhook/6f272d796f1844b8b0b57b61365f8961/2ff46079-ee4a-442b-a642-dc418f6c67ee
Server: YOURCOMPANY.webhook.office.com
URL: /webhookb2/40cc6704-1bc5-4f87-xxxx-xxxxxxxxf@5xxxxxa-643b-47a3-xxxxx-fc962cc7cdb2/IncomingWebhook/6f272d796f1844b8b0b57b61365f8961/2ff46079-ee4a-442b-a642-dc418f6c67ee

Calling to webhook API

The Incoming Webhook app admits the Office 360 connector cards. You can create your card using the adaptivecard designer.

So, I've designed a card to display a error message (Ens.AlertRequest).

 
AdaptiveCard for Ens.AlertRequest

Using this schema, You can create the message using the messages of St.Teams like this

set class=##class(St.Teams.Msg.Adaptive.Request).%New()
set class.Type = "message"
set attach = ##class(St.Teams.Msg.Adaptive.Attachment).%New()
set content = ##class(St.Teams.Msg.Adaptive.Content).%New()

set container = ##class(St.Teams.Msg.Common.Item).%New()
set container.Type = "Container"
set item1=##class(St.Teams.Msg.Common.Item).%New()
set item1.Type = "TextBlock"
set item1.Text = "Unhandled error"
set item1.Weight = "bolder"
set item1.Size = "Medium"
set item2=##class(St.Teams.Msg.Common.Item).%New()
set item2.Type = "TextBlock"
set item2.Text = "St.Teams.BO.MainProcess"
set item2.Weight = "bolder"
set item2.Size = "small"
set item2.IsSubtitle = 1
set item3=##class(St.Teams.Msg.Common.Item).%New()
set item3.Type = "TextBlock"
set item3.Text = "ERROR <Ens>ErrFTPListFailed: 'Unable to open data connection to 127.0.0. on port 8080',código=425)"
set item3.Wrap = 1
set factSet=##class(St.Teams.Msg.Common.Item).%New()
set factSet.Type = "FactSet"
set factItem1 =##class(St.Teams.Msg.Common.FactItem).%New()
set factItem1.Title = "SessionId"
set factItem1.Value = "111"
set factItem2 =##class(St.Teams.Msg.Common.FactItem).%New()
set factItem2.Title = "Time"
set factItem2.Value = "2024-02-28 11:00:15"
do factSet.Facts.Insert(factItem1)
do factSet.Facts.Insert(factItem2)

do container.Items.Insert(item1)
do container.Items.Insert(item2)
do container.Items.Insert(item3)
do container.Items.Insert(factSet)

do content.Body.Insert(container)
set attach.Content = content
do class.Attachments.Insert(attach)

it creates the Json to call to the Webhook. But we want to create the message from a Ens.AlertRequest message, the best way is using a Data Transformer.

Then, the rule of your Ens.Alert should be like this:

It transform the Ens.AlertRequest using the St.Teams.DT.EnsAlertToAdpativeRequest and send it to St.Teams.BO.Api.Teams.

Then you recive the message directly into your Teams group.

I hope it is as useful to you as it has been to us.

15 comentarios
Comentarios (15)4
Inicie sesión o regístrese para continuar
Artículo
· 2 mar, 2024 Lectura de 4 min

IKO - Lessons Learned (Part 1 - Helm)

The IKO documentation is robust. A single web page, that consists of about 50 actual pages of documentation. For beginners that can be a bit overwhelming. As the saying goes: how do you eat an elephant? One bite at a time. Let's start with the first bite: helm.

What is Helm?

Helm is to Kubernetes what the InterSystems Package Manager (IPM, formerly ObjectScript Package Manager - ZPM) is to IRIS.

It facilitates the installation of applications on the platform - in a fashion suitable for Kubernetes. That's to say that it is developed in such a way to facilitate installation to your needs, whether it be a development, test, or production environment.

We provide on our WRC software distribution all you will need under the IRIS Components tab - it consists of a .tar.gz. Extract it and you will get a .tar. Extract it again and you will see a folder iris_operator_<yourversion>. In here are a README with instructions, as well as 3 folders - an image of the IKO (you could have also got this from the InterSystems Container Registry), chart, and samples. Samples is just to help you form your files but is not actually necessary for IKO installation. Chart, however, is necessary. Let's take a peek.

chart
|
|-> iris-operator
               |
               | -> README.md
               | -> .helmignore
               | -> Chart.yaml
               | -> values.yaml
               | -> templates 
                      | -> _helpers.tpl
                      | -> apiregistration.yaml
                      | -> appcatalog-user-roles.yaml
                      | -> cleaner.yaml
                      | -> cluster-role.yaml
                      | -> cluster-role-binding.yaml
                      | -> deployment.yaml
                      | -> mutating-webhook.yaml
                      | -> NOTES.txt
                      | -> service.yaml
                      | -> service-account.yaml
                      | -> user-roles.yaml
                      | -> validating-webhook.yaml
               

 

This is the meat and potatoes (a funny way to say basic ingredients) of the application we will be installing. Don't worry. The only thing that we care about is going to be the values.yaml. Everything else is going on behind the scenes, thanks to Helm. Phew! But it's important to know that though our operator may seem like an ordinary pod, it is a lot more than that.

Most of the contents of the values.yaml are also going to be out of the scope of this article because you will not have to worry about them. We will care about just 4 fields (okay, 5 at most).

They are operator.registry, operator.repository, operator.tag, imagePullSecrets.name[0], and imagePullPolicy.

Where is your IKO image? Is your organization using a private repository? Are you planning on pulling from the ICR? Specify your image details in the registry, repository, and tag fields. If you are using the ICR you can leave it as is.

How will you access the ICR, or your organization repository? Assuming it is private you will need to specify your details with which you can access it for pulling. In the next article I touch on how to create this secret, which we can call intersystems-pull-secret instead of the standard dockerhub-secret which is what is presently there if you downloaded the files from the WRC.

Finally for the imagePullPolicy we can leave it as Always, or alternatively change it to IfNotPresent or Never. I'll refer you to the Kubernetes documentation if you need clarification - here. I tend to use IfNotPresent.

Looks like we're good to go (assuming you already have helm installed, if not install it first)! Let's install the IKO. We are going to need to tell helm where the folder with all our goodies is (that's the iris-operator folder you see above). If we were to be sitting at the chart directory you can use the command

helm install intersystems iris-operator

but perhaps you're sitting a little higher. No problem. This is fine too assuming you are sitting in a repository with iris_operator_amd-3.6.7.100:

helm install intersystems iris_operator_amd-3.6.7.100/chart/iris-operator

You'll get a message that the installation was a success and you can double check your deployment is running as is noted by the message and in our docs.

kubectl --namespace=default get deployments -l "release=intersystems, app=iris-operator"

In the next post we'll put the InterSystems Kubernetes Operator to use.

2 comentarios
Comentarios (2)2
Inicie sesión o regístrese para continuar
Pregunta
· 1 mar, 2024

POST request with paging FHIR bundle

Hi everyone,

I'm looking for a strategy for dividing a large FHIR message, in a post request, into smaller parts. 

I have found the paging modifier for the GET request, but not a similiar one for the POST request. Maybe the 'batch' type of a Bundle could help me to indicate this aim but there aren't any attribute to say the total or the i-th element. 

Do you know of any method for implementing 'paging' in a post request?

2 comentarios
Comentarios (2)2
Inicie sesión o regístrese para continuar
Artículo
· 29 feb, 2024 Lectura de 4 min

Testing Columnar Storage

As most of you probably already know, since approximately the end of 2022 InterSystems IRIS included the columnar storage functionality to its database, well, in today's article we are going to put it to the test in comparison to the usual row storage.

Columnar Storage

What is the main characteristic of this type of storage? Well, if we consult the official documentation we will see this fantastic table that explains the main characteristics of both types of storage (by rows or by columns):

As you can see, columnar storage is designed primarily for analytical tasks in which queries are launched against specific fields in our table, while row storage is more optimal when a large number of insertion, update and deletion operations are required. as well as obtaining complete records.

If you continue reading the documentation you will see how simple it is to configure our table to be able to use columnar storage:

CREATE TABLE table (column type, column2 type2, column3 type3) WITH STORAGETYPE = COLUMNAR

Using this command we would be defining all the columns of our table with columnar storage, but we could opt for a mixed model in which our table has row storage but certain columns make use of columnar storage.

This mixed scenario could be interesting in cases where aggregation operations such as sums, averages, etc. are common. For this case we could define which column is the one that will use said storage:

CREATE TABLE table (column type, column2 type2, column3 type3 WITH STORAGETYPE = COLUMNAR)

In the previous example we defined a table with row storage and a column (column3) with columnar storage.

Comparative

To compare the time spent by column storage and row storage in different queries, we have created a small exercise using Jupyter Notebook that will insert a series of records that we will generate in two tables, the first with storage with rows ( Test.PurchaseOrderRow) and the second with columnar storage in two of its columns (Test.PurchaseOrderColumnar)

Test.PurchaseOrderRow

CREATE TABLE Test.PurchaseOrderRow (
    Reference INTEGER,
    Customer VARCHAR(225),
    PaymentDate DATE,
    Vat NUMERIC(10,2),
    Amount NUMERIC(10,2),
    Status VARCHAR(10))

Test.PurchaseOrderColumnar

CREATE TABLE Test.PurchaseOrderColumnar (
    Reference INTEGER,
    Customer VARCHAR(225),
    PaymentDate DATE,
    Vat NUMERIC(10,2),
    Amount NUMERIC(10,2) WITH STORAGETYPE = COLUMNAR,
    Status VARCHAR(10) WITH STORAGETYPE = COLUMNAR)

If you download the Open Exchange project and deploy it in your local Docker, you can access the Jupyter Notebook instance and review the file PerformanceTests.ipynb, which will be responsible for generating the random data that we are going to store in different phases in our tables and finally it will show us a graph with the performance of the query operations.

Let's take a quick look at our project configuration:

docker-compose.yml

version: '3.7'
services:
  # iris
  iris:
    init: true
    container_name: iris
    build:
      context: .
      dockerfile: iris/Dockerfile
    ports:
      - 52774:52773
      - 51774:1972
    volumes:
    - ./shared:/shared
    environment:
    - ISC_DATA_DIRECTORY=/shared/durable
    command: --check-caps false --ISCAgent false
  # jupyter notebook
  jupyter:
    build:
      context: .
      dockerfile: jupyter/Dockerfile
    container_name: jupyter
    ports:
      - "8888:8888"
    environment:
      - JUPYTER_ENABLE_LAB=yes
      - JUPYTER_ALLOW_INSECURE_WRITES=true
    volumes:
      - ./jupyter:/home/jovyan
      - ./data:/app/data
    command: "start-notebook.sh --NotebookApp.token='' --NotebookApp.password=''" 

We deploy the IRIS and Jupyter containers in our docker, initially configuring IRIS with the namespace "TEST" and the two tables required for the test.

To avoid boring you with code, you can consult the PerformanceTests.ipynb file from which we will connect to IRIS, generate the records to be inserted and store them in IRIS

Test execution

The results have been the following (in seconds):

Inserts:

The insertions made are of bulk type:

INSERT INTO Test.PurchaseOrderColumnar (Reference, Customer, PaymentDate, Vat, Amount, Status) VALUES (?, ?, ?, ?, ?, ?)

And the time for each batch of inserts is as follows:

Total inserts

Row storage Mixed storage
1000

0.031733

0.041677

5000

0.159338

0.185252

20000

0.565775

0.642662

50000

1.486459

1.747124

100000

2.735016

3.265492

200000

5.395032

6.382278

Selects:

The Select launched includes an aggregation function and a condition, both on columns with columnar storage:

SELECT AVG(Amount) FROM Test.PurchaseOrderColumnar WHERE Status = 'SENT'

Total rows

Row storage Mixed storage
1000

0.002039

0.001178

5000

0.00328

0.000647

20000

0.005493

0.001555

50000

0.016616

0.000987

100000

0.036112

0.001605

200000

0.070909

0.002738

Conclusions

As you can see in the results obtained, the operation is exactly what is indicated in the documentation. Including columns with columnar storage has slightly penalized performance during insert (about 18% slower for our example) while queries on those same columns have dramatically improved response time (258 times faster).

It is undoubtedly something to take into account when planning the development of any application.

4 comentarios
Comentarios (4)1
Inicie sesión o regístrese para continuar
Artículo
· 29 feb, 2024 Lectura de 5 min

Poniendo a prueba el Columnar Storage

Como seguramente ya sabréis la mayoría de vosotros, desde aproximadamente finales de 2022 InterSystems IRIS incluyo la funcionalidad de almacenamiento columnar a su base de datos, pues bien, en el artículo de hoy vamos a ponerla a prueba en comparación con el almacenamiento en filas habitual.

Almacenamiento columnar

¿Cuál es la principal característica de este tipo de almacenamiento? Pues bien, si consultamos la documentación oficial veremos esta fantástica tabla que nos explica las principales características de ambos tipos de almacenamiento (por filas o por columnas):

Como véis el almacenamiento columnar está pensado sobretodo para tareas de tipo analítico en las que se lanzan consultas contra campos específicos de nuestra tabla, mientras que el almacenamiento por filas es más óptimo cuando se requiere realizar gran número de operaciones de inserción, actualización y eliminación, así como obtener registros íntegros.

Si seguís leyendo la documentación veréis lo sencillo que es configurar nuestra tabla para poder utilizar el almacenamiento columnar:

CREATE TABLE table (column type, column2 type2, column3 type3) WITH STORAGETYPE = COLUMNAR

Mediante este comando estaríamos definiendo todas las columnas de nuestra tabla con almacenamiento columnar, pero podríamos decantarnos por un modelo mixto en el que nuestra tabla tenga un almacenamiento en filas pero que determinadas columnas hagan uso del almacenamiento columnar.

Este escenario mixto podría ser interesante en casos en los que sean habitual operaciones de agregación como sumas, medias, etc. Para este caso podríamos definir que columna es la que hará uso de dicho almacenamiento:

CREATE TABLE table (column type, column2 type2, column3 type3 WITH STORAGETYPE = COLUMNAR)

En el ejemplo anterior definiríamos una tabla con almacenamiento por filas y una columna (column3) con almacenamiento columnar.

Comparativa

Para realizar la comparativa entre el tiempo empleado por almacenamiento columnar y el de por filas en diferentes consultas hemos creado un pequeño ejercicio haciendo uso de Jupyter Notebook que va a insertar una serie de registros que generaremos en dos tablas, la primera con almacenamiento con filas (Test.PurchaseOrderRow) y la segunda con almacenamiento columnar en dos de sus columnas (Test.PurchaseOrderColumnar)

Test.PurchaseOrderRow

CREATE TABLE Test.PurchaseOrderRow (
    Reference INTEGER,
    Customer VARCHAR(225),
    PaymentDate DATE,
    Vat NUMERIC(10,2),
    Amount NUMERIC(10,2),
    Status VARCHAR(10))

Test.PurchaseOrderColumnar

CREATE TABLE Test.PurchaseOrderColumnar (
    Reference INTEGER,
    Customer VARCHAR(225),
    PaymentDate DATE,
    Vat NUMERIC(10,2),
    Amount NUMERIC(10,2) WITH STORAGETYPE = COLUMNAR,
    Status VARCHAR(10) WITH STORAGETYPE = COLUMNAR)

Si os descargais el proyecto de Open Exchange y lo desplegáis en vuestro Docker local podréis acceder a la instancia de Jupyter Notebook y revisar el archivo PerformanceTests.ipynb el cual será el encargado de generar los datos aleatorios que vamos a almacenar en diferentes fases en nuestras tablas y finalmente nos mostrará una gráfica con el rendimiendo de las operaciones de consulta.

Echemos un vistazo rápido a la configuración de nuestro proyecto:

docker-compose.yml

version: '3.7'
services:
  # iris
  iris:
    init: true
    container_name: iris
    build:
      context: .
      dockerfile: iris/Dockerfile
    ports:
      - 52774:52773
      - 51774:1972
    volumes:
    - ./shared:/shared
    environment:
    - ISC_DATA_DIRECTORY=/shared/durable
    command: --check-caps false --ISCAgent false
  # jupyter notebook
  jupyter:
    build:
      context: .
      dockerfile: jupyter/Dockerfile
    container_name: jupyter
    ports:
      - "8888:8888"
    environment:
      - JUPYTER_ENABLE_LAB=yes
      - JUPYTER_ALLOW_INSECURE_WRITES=true
    volumes:
      - ./jupyter:/home/jovyan
      - ./data:/app/data
    command: "start-notebook.sh --NotebookApp.token='' --NotebookApp.password=''" 

Desplegamos en nuestro docker los contenedores de IRIS y Jupyter, configurando al inicio IRIS con el namespace "TEST" y las dos tablas requeridas para la prueba.

Para no aburriros con código podéis consultar vosotros mismos el archivo PerformanceTests.ipynb desde el que nos conectaremos a IRIS, generaremos los registros a insertar y los almacenaremos en IRIS

Ejecución de las pruebas

Los resultados han sido los siguientes (en segundos):

Inserciones:

Las inserciones realizadas son de tipo bulk:

INSERT INTO Test.PurchaseOrderColumnar (Reference, Customer, PaymentDate, Vat, Amount, Status) VALUES (?, ?, ?, ?, ?, ?)

Y el tiempo para cada lote de inserciones es el siguiente:

Total de inserciones

Almacenamiento por filas Almacenamiento mixto
1000

0.031733

0.041677

5000

0.159338

0.185252

20000

0.565775

0.642662

50000

1.486459

1.747124

100000

2.735016

3.265492

200000

5.395032

6.382278

Consultas:

La consulta lanzada incluye una función de agregación y una condición, ambas sobre las columnas con almacenamiento columnar: 

SELECT AVG(Amount) FROM Test.PurchaseOrderColumnar WHERE Status = 'SENT'

Total de filas

Almacenamiento por filas Almacenamiento mixto
1000

0.002039

0.001178

5000

0.00328

0.000647

20000

0.005493

0.001555

50000

0.016616

0.000987

100000

0.036112

0.001605

200000

0.070909

0.002738

Conclusiones

Como veis en los resultados obtenidos, el funcionamiento es exactamente a lo que se indica en la documentación. La inclusión de columnas con almacenamiento columnar ha penalizado levemente el rendimiento durante la inserción (un 18% más lento aproximadamente para nuestro ejemplo) mientras que las consultas sobre esas mismas columnas han mejorado espectacularmente el tiempo de respuesta (258 veces más rápida).

Sin duda es algo a tener en cuenta en el momento de planificar el desarrollo de cualquier aplicación.

2 comentarios
Comentarios (2)1
Inicie sesión o regístrese para continuar