Quantcast
Channel: APEX-AT-WORK by Tobias Arnhold

After the party is before the party

$
0
0
Now two months after the #pougtrip everything went back to normal. Except the planning for the next events.

The DOAG #NextGEN is currently starting a "roadshow" at German universities.

Start is the 07.11.2017 at the Trier University of Applied Sciences. We will have 3 presentations covering different technologies: SQL, JS, JSON, SVG, PL/SQL, REST and APEX.
But see for yourself: Oracle Vorträge an der Hochschule Trier


As you can see the agenda is made with APEX :). Inspired by the #pougtrip we plan free beer for all students!

This is our first test run to find out what it takes to get as much students as possible to listen to Oracle related technologies.

From my perspective the question is: What can an Oracle expert contribute to young professionals?

For example: handling real life problems and sharing the experience while using modern technologies. But I think most important is devotion.
You get inspired by an expert mostly depending on how dedicated this person is delivering the information. This event gives us the chance to inspire young professionals to see Oracle in a new light. See the possibilities and feel the devotion by the people who are working with the technology.

BTW: This event wouldn't be possible without two dedicated students. Thanks Rebecca and Abby!

Dynamic LOV with Pipeline function

$
0
0
A new year brought me some new tasks. I had to take over a generic Excel import and the customer wanted some extension by checking if the join on the master tables were successful.

Unfortunate we were talking about a generic solution which meant that all the configuration was saved inside tables including the LOV-tables which were saved as simple select statements.

Goal:
Show all import rows/values which were not fitting towards the master data.

How did I fix it?


Source of LOV data:

Source of import data:


I made a little abstract data model so that you understand what I mean:
I have two tables "I_DATA" including the values from the import and "I_DYNAMIC_SQL" including the LOV statements.

-- ddl
CREATE TABLE "I_DYNAMIC_SQL"
( "ID" NUMBER NOT NULL ENABLE,
"SQL_STATEMENT" VARCHAR2(4000),
CONSTRAINT "I_DYNAMIC_SQL_PK" PRIMARY KEY ("ID")
USING INDEX ENABLE
) ;

CREATE TABLE "I_DATA"
( "ID" NUMBER NOT NULL ENABLE,
"DATA_VALUE" VARCHAR2(1000),
"DYNAMIC_SQL_ID" NUMBER,
"DATA_GROUP" VARCHAR2(20),
CONSTRAINT "I_DATA_PK" PRIMARY KEY ("ID")
USING INDEX ENABLE
) ;

-- data
REM INSERTING into I_DATA
SET DEFINE OFF;
Insert into I_DATA (ID,DATA_VALUE,DYNAMIC_SQL_ID,DATA_GROUP) values (1,'Jonas',1,'G1');
Insert into I_DATA (ID,DATA_VALUE,DYNAMIC_SQL_ID,DATA_GROUP) values (2,'Sven',1,'G2');
Insert into I_DATA (ID,DATA_VALUE,DYNAMIC_SQL_ID,DATA_GROUP) values (3,'Annika',1,'G3');
Insert into I_DATA (ID,DATA_VALUE,DYNAMIC_SQL_ID,DATA_GROUP) values (4,'Jens',1,'G4');
Insert into I_DATA (ID,DATA_VALUE,DYNAMIC_SQL_ID,DATA_GROUP) values (5,'FH Trier',2,'G1');
Insert into I_DATA (ID,DATA_VALUE,DYNAMIC_SQL_ID,DATA_GROUP) values (6,'TH Bingen',2,'G1');
Insert into I_DATA (ID,DATA_VALUE,DYNAMIC_SQL_ID,DATA_GROUP) values (7,'FH Trier',2,'G2');
Insert into I_DATA (ID,DATA_VALUE,DYNAMIC_SQL_ID,DATA_GROUP) values (8,'TH Bingen',2,'G2');
Insert into I_DATA (ID,DATA_VALUE,DYNAMIC_SQL_ID,DATA_GROUP) values (9,'Frankfurt UAS',2,'G3');
Insert into I_DATA (ID,DATA_VALUE,DYNAMIC_SQL_ID,DATA_GROUP) values (10,'TH Bingen',2,'G4');

REM INSERTING into I_DYNAMIC_SQL
SET DEFINE OFF;
Insert into I_DYNAMIC_SQL (ID,SQL_STATEMENT) values (1,'select d,r from (
select ''Jonas'' as d, 1 as r from dual union all
select ''Sven'' as d, 2 as r from dual union all
select ''Jens'' as d, 3 as r from dual union all
select ''Annika'' as d, 4 as r from dual
)');
Insert into I_DYNAMIC_SQL (ID,SQL_STATEMENT) values (2,'select d, r
from (
select ''FH Trier'' as d, 1 as r from dual
union all
select ''TH Bingen'' as d, 2 as r from dual
)');

It actually took some time to find a solution fitting my needs.
1. Fast
2. Easy to understand
3. Not tons of code

What I needed was some kind of EXECUTE IMMEDIATE returning table rows instead of single values. With pipeline functions I was able to do it:
create or replace package i_dynamic_sql_pkg as

/* LOV type */
type rt_dynamic_lov is record ( display_value varchar2(4000), return_value number );

type type_dynamic_lov is table of rt_dynamic_lov;

function get_dynamic_lov (
p_lov_id number
) return type_dynamic_lov pipelined;
end;

create or replace package body i_dynamic_sql_pkg as
/* global variable */
gv_custom_err_message varchar2(4000);

/* Function to return dynamic lov as table */
function get_dynamic_lov (
p_lov_id number
) return type_dynamic_lov pipelined is

row_data rt_dynamic_lov;

type cur_lov is ref cursor;
c_lov cur_lov;

e_statement_exist exception;

v_sql varchar2(4000);
begin

-- 'Exception check - read select statement';
select
max(sql_statement)
into
v_sql
from i_dynamic_sql
where id = p_lov_id;

-- 'Exception check - result';
if v_sql is null
then
gv_custom_err_message := 'Error occured. No list of value found.';
raise e_statement_exist;
end if;

-- 'Loop dynamic SQL statement';
open c_lov for v_sql;
loop
fetch c_lov
into
row_data.display_value,
row_data.return_value;
exit when c_lov%notfound;

pipe row(row_data);
end loop;
close c_lov;

exception
when e_statement_exist then
rollback;
/*
apex_error.add_error(
p_message => gv_custom_err_message
, p_display_location => apex_error.c_inline_in_notification
);
*/
raise_application_error(-20001, gv_custom_err_message);
when others then
raise;
end;
end;

Now I just had to create a SQL statement doing the job for me:
-- ddl
select
da.data_group,
da.data_value,
/* check if a return value exist */
case
when lov.display_value is not null
then 'OK'
else 'ERROR'
end as chk_lov_data_row,
/* apply error check for the whole group */
case
when min(case
when lov.display_value is not null
then 1
else 0
end) over (partition by da.data_group)
= 0
then 'ERROR'
else 'OK'
end as chk_lov_data_group
from i_data da
/* Join on my pipeline function including the dynamic sql id */
left join table(i_dynamic_sql_pkg.get_dynamic_lov(da.dynamic_sql_id)) lov
on (da.data_value = lov.display_value)
order by da.data_group, da.dynamic_sql_id, da.data_value;

Result:

APEX ist anders. Genau das gleiche gilt auch für die APEX Connect!

$
0
0
Es gibt zwei Dinge die ich in über 10 Jahren im Oracle Umfeld immer und immer wieder höre:

1. Die Community wird immer älter.
2. APEX ist aus Grund XY schlecht.

Was hat die APEX Connect damit zu tun?
Die APEX Connect beweist nun zum 4ten Mal, dass eine Community (wie die Technologie selbst) nicht stehenbleiben darf. Ohne die gemeinsame Arbeit an immer wieder neuen Angeboten wäre die Veranstaltung wohl nicht das was sie heute ist.

Deshalb gibt es dieses Jahr erstmals einen eigenen Track für Neueinsteiger, der von einigen DER APEX Experten präsentiert wird. Das bedeutet, Du bekommst als DOAG-Mitglied für 208 € nicht nur einen umfassenden Einstieg in die aktuelle APEX Version, sondern kannst Dir gleichzeitig noch die besten Tipps und Tricks vom Who is Who der Branche abholen.



Dabei ist es egal, ob Du Student, Azubi, ehemaliger DBA, Formsentwickler oder Quereinsteiger bist. Dieser Track ist für alle gleichermaßen perfekt geeignet: APEX for beginners am 24.04.2018


Ein eigener Newcomer-Track bedeutet aber noch viel mehr: Es öffnet die Tore für die neue Generation an APEX-Entwicklern.
Und hier ist die Community gefragt, denn neben den Studentengruppen, die über #NextGEN-Roadshows an den Universitäten bei der Connect teilnehmen, könnt ihr euren Auszubildenden oder Studenten für nur 340 € an allen drei Tagen der Konferenz inklusive Übernachtung und ÖPNV vor Ort teilnehmen lassen.


Wann gab es das schon mal, dass ein Oracle Event für die nächste Generation ein so vollumfängliches Gesamtpaket geliefert hat?
In 10 Jahren als Oracle Experte habe ich noch keines erlebt!

Nun zurück zum Anfang! Ich seh das so:
Die APEX und PL/SQL Community kämpft um die nächste Generation und bietet den kinderleichten Einstieg den es dafür braucht. Dazu gehört Mut, Begeisterung und Voraussicht. Dank dem Einsatz der DOAG, der #NextGEN-Community und den APEX Connect Verantwortlichen ist zumindest im Development-Umfeld der Verjüngungsprozess längst eingetreten.

Warum eine Technologie-Konferenz für einen jungen ITler so wichtig ist, beschreibt Carolin Hagemann:
"Ich selbst habe gerade am Anfang meiner APEX-Zeit als Student an der DOAG K+A teilgenommen. Neben den Erfahrungen persönlich mit den Referenten sprechen zu können (ich habe den "APEX-Raum" betreut) und direkt am Puls der Technologie sein zu können, hat mir gerade diese Zeit auch sehr bei dem Einstieg in die Community geholfen.
Und neben diesen doch eher weichen Faktoren gibt es durchaus auch andere Gründe an den Veranstaltungen teilzunehmen: Ist man erst einmal in dem Sog der APEX-Community gefangen, weiß man von den neusten Releases nur wenige Stunden nach der Veröffentlichung. Man profitiert von den Kontakten und bekommt auch schon einmal 20 Minuten nachdem man einem der APEX-Entwickler eine Frage gestellt hat, eine sehr ausgiebige Antwort ;) Das kann in vielen Situationen ein entscheidender Vorteil sein."

Jetzt seid ihr gefragt und gebt euren Azubis und Studenten die Möglichkeit teilzunehmen oder sponsert eine Wildcard, um ein neues Gesicht in unsere Community zu integrieren. Jeder der sich einmal mit dem APEX Virus infiziert hat, weiß wie schwierig es ist hiervon wieder los zu kommen. Also lasst uns gemeinsam möglichst viele neue Leute infizieren!


---
Danke an Carolin Hagemann und Matthias Nöll für deren wertvollen Input in den Artikel.

---
Links:
Werde Teil der NextGEN Community, dann schreibe einfach eine Mail an nextgen@doag.org

Interactive Report - Column background color

$
0
0
One of my customers needed an IR where half of the report columns should be visualized in another color.


It is really easy to integrate. Go into the report column attributes inside your "Page Designer" and add a "Static ID" for each column:

Column 1: rep_col_diffcolor_1
Column 2: rep_col_diffcolor_2
...


Now add this CSS snippet inside the page attributes:

.a-IRR-table tr td[headers*="rep_col_diffcolor_"]
{
    background-color: #99ccff;
}


The trick is to address every element by searching for the start part of the "Static ID" name.

CREATE or COPY master data pages in APEX

$
0
0
In this blog post I just want to give a hint about the positive and negatives effects when you COPY master data pages for different master data tables.

Example:
You have two tables: DOCUMENT_CATEGORY and ORDER_CATEGORY
Both tables have the same columns :
ID, CATEGORY_NAME, DESCRIPTION
CREATED_ON, CREATED_BY, CHANGED_ON, CHANGED_BY

In APEX you have a Master - Detail view including 2 pages: report view and modal dialog


If you decide to copy both pages you have to adjust the following things:
Master Data Report:
 - Page Name
 - Breadcrumb
 - Navigation Menu
 - Report DDL: Table Name
 - Report Column Link
 - Create Button Link

Modal Dialog:
 - After Header - Automatic Row Fetch (Name, Table Name)
 - Processing - Automatic Row Processing (Name, Table Name)
 - After Processing - Branch
 - APEX Items (in case you have different names or use prefixes)

What is the advantage?
1. Security
Security settings get copied like "Page Authorization Scheme". Master data pages normally apply to only one role, for example: administration
2. Labels and messages get copied (items, buttons). Really helpful if you named them differently to the standard APEX naming.

What is the disadvantage?
1. Linking
If you forget to change the branches/links then you could end up on the wrong page changing the wrong data. But on the other side this is the first thing you check after you finished the editing of the pages.
2. Automatic processing
If you forget to update the table names then you end up changing the wrong data.

Conclusion:
It is a bit more risky to copy master data pages but it can save you a lot of time changing label names and to remember the security settings.

Join the NextGEN group at APEX Connect 18

$
0
0
The #NextGEN community is planning an after conference evening activity. For that reason we published a German article with all details on doag.org. My dear friend Jonas translated the text via deepl.com/translator.

Original article:
Wir sind EINE Community!

Here you go:

------

Young people who are motivated to get involved in conference planning? Yes, that's right!
And these motivated youngsters bring 24 more youngsters far away from the Oracle APEX universe.
Before the "older ones" among us now fall into shock rigidity - stay relaxed, because we need you!

These 24 students have no idea of our special APEX community.
It's up to us to take them along and show them how much fun, passion and action they can expect in APEX everyday life.

How do you recognize the students? 
Long hair, pale skin, no crutch... well, seriously: all students wear orange bracelets.

What do we expect from you?
Talk to the students! Don't just make them feel like they're in the community, but show them what it means to live the APEX community membership.

How do you do it best?
In addition to the conference and community evening on Wednesday, there are other networking opportunities:
For example, anyone interested can meet after the Speaker Reception on Tuesday at Schweinske (Bolkerstr. 28) from 8:30 p.m. in Düsseldorf's Old Town.
On Wednesday, after the official evening event at the Apollo Varieté, a joint visit to Club El Papagayo, which you can find at Mertensgasse 2 (both on a self-payer basis).

A tip: Give the poor students a drink - this will certainly increase their attention to want to know something about APEX! :slightly_smiling_face:

With these requests and advice we hope as NextGEN to bring the young savages together with the established community.

See you at APEX Connect!

Your NextGen team:

Abby, Caro, Rebecca, Davide, Jonas, Matthias, Philipp, Sebastian and Tobias

------

See on Tuesday! =)

Check inside your APEX application if debug mode is enabled

$
0
0
Sounds like a simple task but whenever I have the requirement to add a region and make it conditional to check if APEX is running in debug mode. I always search for half an hour finding the right solution.

Search example on Google: "Oracle APEX check debug mode conditional PL/SQL"
Trying this or 30 different other ways it always ends up with the wrong results.

But it is so easy - Conditional PL/SQL Expression:
APEX_APPLICATION.G_DEBUG

G_DEBUG returns true or false!
The documentation is a bit imprecise because it says 'Yes' or 'No'.

Anyway it is all well documented in the APEX documentation
> APEX_APPLICATION > Global Variables:
https://docs.oracle.com/database/apex-18.1/AEAPI/Global-Variables.htm#AEAPI29544




Working with the APEX tree

$
0
0
Out of a coincidence I haven't used the APEX tree region for years. Now I got the task to create a customizable tree in my application. Since APEX 5 there is a new tree type called "APEX tree" which supports some really cool functions.

Anyway I had to look around to find out what the APEX tree is actually capable of. First of all start with the APEX "Sample Trees" application which you find in the packaged application area.



Morten Braten took that example and described the features really well:
https://ora-00001.blogspot.com/2016/01/working-with-the-apex-5-treeview.html

John Snyders from the APEX team also created 2 blogposts about the "APEX tree":
APEX 5.0 Converting to the new APEX Tree
Add Checkbox Selection to APEX Tree Region

And as he mentioned there is a Javascript library behind it "libraries/apex/widget.treeView.js." which is documented since APEX 18.1 or at least I think it is. Anyway here is a link which I also have to further investigate:
JS Doc: Widget: treeView

German users can read this document provided by MT AG:
http://files.xmasman.de/20160216_MeetupNRW_TillAlbert_WiePflanzeIchEinenBaumApex.pdf

Enable save button on form change

$
0
0
Today I had the requirement that the save button should stay disabled until a form item changed.

After digging around I found a quite easy solution which worked well until now.

Save Button
Static ID: saveBtn
Custom Attributes: disabled

Dynamic Action
Event: Page Load
Execute Javascript Code:
$('#wwvFlowForm').on('input change', function() {
    $('#saveBtn').attr('disabled', false);
});



Simple but effective.

Copy and Paste to clipboard

$
0
0
Well I had the requirement to copy the content of a textarea into the clipboard. There are two ways to do that:

1. Build a dynamic action with custom Javascript code:
Copy Text to Clipboard

Code example - with dynamic action on "Click" and "Execute Javascript Code":
/* Select the text field */
$('#P1_APEX_ITEM').select();

/* Copy the text inside the text field */
document.execCommand("copy");



2. Use an APEX plugin:
Copy to Clipboard (v1.1) - build by Dick Dral



Icons made by Vitaly Gorbachev from www.flaticon.com is licensed by CC 3.0 BY

Set APEX application name for Dev, Test and Prod environment in the same database

$
0
0
In case you have a small application where development, test and maybe also production environment are on the same database and your applications in this environment distinguish only by the application IDs. To setup a custom application name based on the ID you could do like this:


We assume our application name is "Training room app" defined in the "Shared Components"> "User Interface Attributes"


To differentiate the environments I add a dynamic action "Page Load" on Page 0.
This dynamic action is executing custom Javascript code:

if ('&APP_ID.' == '200') {
  $('.t-Header-logo').find('span').html('Training room app - <b style="color:#008A34">Test Environment</b>');
}
else if ('&APP_ID.' == '300') {
  $('.t-Header-logo').find('span').html('Training room app - <b style="color:#9366a5">Development Environment</b>');
}


The code is changing the name of the logo area.

 

#nextGENTrip18 a Twitter story

$
0
0
I would like to give you a feeling about our #NextGEN community activities by showing you a collection of tweets at the latest event which we participated:

The #ApexDay2018 in Stockholm at the SWEOUG

We have been 12 IT enthusiast from different places and different life circumstances.
 - 6 women and 6 men
 - 5 students and 5 employees and 2 freelancer
 - 9 Oracle specialists and 7 of them working primary with APEX


Goal

Get everyone further engaged in #NextGEN activities and inspire new IT enthusiasts to join our community/activities/events.

Follow DOAG #NextGEN

Website: https://www.doag.org/de/nextgen/nextgen/
Twitter: https://twitter.com/doagnextgen
Instagram: https://www.instagram.com/doagnextgen

The Twitter story




























5 reasons why 








You came this far... That can only mean that you want to join us.
We have a Slack channel where we organize new events and help each other on different topics. Especially APEX newcomer get a lot of support. Slack channel communication is in German. If you want to join then just write us an email: nextgen@doag.org


Configure the SQL Developer on Mac OS X for AWS Cloud access

$
0
0
I have struggled a while now to correctly configure my Mac so that I can access an Oracle database in the AWS cloud.

Got a couple of strange connection errors:
I/O-Fehler: General SSLEngine problem
Handshake error

I even created a question in the forum:
https://community.oracle.com/message/14924079#14924079
At the end my friend Rüdiger helped me finding the right solution.

What was my configuration:
 - Max OS X 'El Captain'
 - JDK version: Build 1.8.0_181-b13
 - SQL Developer version:  Version 18.2.0.183
 - AWS cloud connectivity via SSL
 - Oracle database

Step by step:

1. Create the certificate "rds-ca-2015-root.der":
https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appendix.Oracle.Options.SSL.html

2. Get your current JDK version:
java -version 
java version "1.8.0_181"

3. Copy the file CER in your JAVA_HOME/jre/lib/security
Full HOME directory: /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home

4. Open a terminal window and go into that directory:
cd /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/security

5. Execute keytool
keytool -import -alias rds-root -keystore cacerts -file rds-ca-2015-root.der

6. Start your SQL Developer and create a new connection


Connection type: Advanced
JDBC-URL:
jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=dbxe.xyz.server-center1.rds.amazonaws.com)(PORT=7575))(CONNECT_DATA=(SERVICE_NAME=DBXE)))

Flagge zeigen - IT gegen Rechts

$
0
0
Ich habe lange gezögert und überlegt… Es ist aber Zeit! Zeit mich zu positionieren, damit jedem klar ist, für welche Form von IT ich stehe. In Zeiten wie diesen ist es nicht mehr möglich, sich einfach weg zu ducken und zu hoffen!
Dazu möchte ich euch kurz etwas über die Geschichte meiner Familie erzählen:
Zwei meiner Urgroßväter haben im 2. Weltkrieg gekämpft - der eine als Freiheitskämpfer für den Kommunismus und der andere als Soldat für die Wehrmacht.

Das Ergebnis:
Tod, Leid und Vertreibung.

Und heute?
Ich selbst bin auch in einer gewissen Art und Weise geflüchtet… Geflüchtet von diesem sinnlosen Spruch "sei froh, dass du Arbeit hast". Seit ich im Westen von Deutschland lebe, habe ich viele Menschen kennengelernt und nur wenige sind mir mit "Anti-Ossi"-Sprüchen begegnet, dafür viele neue Farben, Namen und Lebenseinstellungen, die ich so bisher nicht kannte. Dieses sogenannte "Multi-Kulti" und die zumeist vorurteilsfreie / offene Gesellschaft war eine Bereicherung für meine eigene Entwicklung, sowohl im IT Bereich als auch im normalen Leben.

Ich hatte das Glück Menschen aus allen Teilen der Welt kennenlernen und schätzen lernen zu dürfen.

Die Vorstellung, dass Menschen in unserem Land zukünftig gejagt, vertrieben oder getötet werden, lässt mich innerlich kalt erschauern. Wer nach Chemnitz meint, dass diese Angst an den Haaren herbeigezogen sei, hat die Lage noch nicht erkannt.

Ich für meinen Teil stehe für eine IT des Miteinanders, des Austauschs und der Zusammenarbeit unabhängig von Hautfarbe, Herkunft, Religion, Geschlecht und sexueller Orientierung. Ich bewerte den einzelnen Menschen nach dessen Fähigkeiten, ob und wie ich mit diesen zusammenarbeiten kann und will.
Es ist jetzt Zeit, dass wir Zeichen setzen, egal ob als Einzelperson, Verein oder Firma.
Bald schon könnte die Angst unsere Entscheidungen zu sehr beeinflussen.

Daher möchte ich gern auf die "Pride in London" referenzieren, diese sollte ein Vorbild für unser zukünftiges Handeln auch im IT Umfeld darstellen.

Bild von der #pridelondon2018 - Oracle OPEN (LGBTQ+)
Denn leider ist es nicht mehr nur Ausgrenzung für die wir auf die Straße müssen, jetzt ist unsere Demokratie gefährdet.

Ps.:
Dieser Kommentar von spiegel.de trifft meine Sicht der Dinge sehr gut:
http://www.spiegel.de/politik/deutschland/afd-wer-sie-waehlt-waehlt-nazis-a-1226160.html

APEX client side error messages - apex.message.showErrors

$
0
0
Since APEX 5.1 we have the ability to create client side messages without the requirement to use a plugin or custom code to create nice looking client side messages.

For that requirement the APEX team created the apex.message library.

This library provides the same look and feel as if you would create a message out of your Page Designer > Validation Area.



In this example I will tell you something about a more advanced way to use the client side error messages.

As the documentation says this is the default way to create a client side error message:

/* General page error message */
apex.message.showErrors([
{
type: "error",
location: "page",
message: "Page error has occurred!",
unsafe: false
}
]);
  /* Item error message */ apex.message.showErrors([
{
type: "error",
location: [ "page", "inline" ],
pageItem: "P1_ITEM",
message: "Value is required!",
unsafe: false
}
]);

Example for a page error message:




In my example I want to check more then one item. For that I created an array including all item names I wanted to check. Within a "FOR" loop I run through every item and created an error message if the value was NULL. If no error occurred I run another custom dynamic action.

apex.message.clearErrors();

var chkErr = 0;
var arr = [
'P1_STRASSE',
'P1_HAUS_NR',
'P1_PLZ',
'P1_ORT',
'P1_ITEM_XYZ'
];

for (var i in arr) {
if ($v(arr[i]).length == 0) {
apex.message.showErrors([
{
type: apex.message.TYPE.ERROR,
location: ["inline"],
pageItem: arr[i],
message: "
Value is required!",
unsafe: false
}
]);
chkErr = 1;
}
}

if ( chkErr == 0 ) { 
/* Custom dynamic action call when no error occurred */
apex.event.trigger(document, 'customDA', [{customAttribute:'1'}]);
}
You want to know more? Check out the documentation and this series of blog posts by Martin D'Souza:
https://www.talkapex.com/2018/03/custom-apex-notification-messages/
https://www.talkapex.com/2018/03/how-to-save-page-data-but-show-errors-in-apex/

Eine verrückte Zeit endet...

$
0
0
Hallo zusammen,

ich habe leider eine schlechte Nachricht zu verkünden. Ich trete als Leitungskraft innerhalb der DOAG NextGen-Community zurück. Das kommt leider nicht nur für euch überraschend, und glaubt mir, dieser Schritt ist mir nicht leichtgefallen, weil ich weiß, dass ihr da draußen auf das, was die NextGen geleistet hat, vertraut und mit Freude die Entwicklung und den Spirit mitverfolgt habt.

Leider gab es vor kurzem mehrere Vorstandsbeschlüsse, die ein weiteres Engagement für mich praktisch unmöglich machen. Diese Beschlüsse betreffen die grundsätzliche Ausrichtung der NextGen-Community innerhalb der DOAG.

Beschluss 1:
„Die #NextGen-Community soll nur Themen behandeln, die NICHT bereits durch Themenverantwortliche in anderen Communitys abgedeckt werden.“

Heißt, die Technologie APEX darf nicht mehr mit der NextGen in Verbindung gebracht werden.

Beschluss 2:
„Für die Nachwuchsförderung bei bestehenden Themen sollen die jeweiligen Themenverantwortlichen der jeweiligen Communitys verantwortlich sein, nicht die #NextGen. Eine Zusammenarbeit und Einbindung in die #NextGen kann bestehen bleiben.“

Heißt für die Technologie APEX, dass Niels aus der Development-Community für APEX für den Nachwuchs verantwortlich ist.

Was macht die NextGen dann zukünftig?
Die DOAG verfolgt das Ziel, sich neuen, auch Oracle-unabhängigen Technologien zu öffnen und genau hier soll, denke ich, die NextGen in Zukunft der Treiber sein. Weitere Details dazu dann von der NextGen auf der DOAG K+A.

Was bedeutet das für die APEX-Community?
Ganz ehrlich, zunächst einmal einen großen Rückschlag. Die Leute, die im APEX Umfeld sind und gleichzeitig das gemocht haben, was die NextGen bisher verkörpert hat, diesen jungen Tech Enthusiasts muss die DOAG Development-Community nun schnell und einfach den Einstieg in deren Community bereiten, ansonsten werden diese Leute abspringen und sich anderweitig engagieren. Ich rede hier nicht von mir, sondern von denen die ich auf meiner Reise getroffen und schätzen gelernt habe:
Jonas, Charlotte, Pierre, Caro, Matthias, Jan, Davide, Ali, Philipp, Felix, Louisa, Rebecca, Maximilian, Victoria, Sebastian, Steven, Christopher, Abby, Florian, um nur einige zu nennen.

Und meine Zukunft?
Ich werde meinen Spirit und meinem Community-Fokus beibehalten und über andere Wege das Besondere tun um diese einzigartige Community zu bereichern.

Das Ergebnis werdet ihr auf der APEX Connect 2019 sehen, weil ich ab der APEX Connect 2019 für den Track APEX die Verantwortung innehabe. Und hier gibt es natürlich nur ein Ziel: den Community-Spirit an die Spitze zu treiben und mit möglichst vielen APEX-Enthusiasten eine unvergessliche Konferenz auf die Beine zu stellen.

Außerdem unterstütze ich das FABE Projekt (forallabeautiful.earth) von Steven Feuerstein und Vincent Morneau aus Sicht der Datenmodellierung.

Der NextGen-Community selbst kann ich nur alles Gute und viel Erfolg auf dem neuen Weg wünschen. Mir werden diese besonderen Momente immer im Herzen erhalten bleiben und ich bin sehr dankbar dafür solch einen Community-Spirit erlebt haben zu dürfen. Menschen wie Carolin Hagemann, Davide Groppuso, Jonas Gassenmeyer, Matthias Nöll und viele Andere die dem Wort "Community" den Spirit eingehaucht haben den ich geliebt habe.

Danke für eine GEILE Zeit! Und auch Danke Ingo Sobik für das unterstützen von verrückten Aktionen und dem ganzen DOAG Office diese Aktionen dann am Ende gemeinsam auch in die Tat umgesetzt zu haben.

Never forget: #POUGTrip, #nextGENrocks #NextGENTrip18 #Roadshow # CommunitySpirit




Interactive Grid - Column stretching

$
0
0
Since APEX 18.1 you can define the stretch behavior per column.

You can choose between 3 different options:

Use Report Setting
The column will use the Stretch Report Setting set by the End User to define if the column should stretch or not.
In APEX 18.1 you have a new option to let the end user decide if the columns should be stretched or not.
This option effects all columns by default.

Never
The column will not stretch and always just use the width specified. This is useful for columns with short content like Yes/No or Numbers.
This option will over rule "Use Report Setting" and is really good for link columns or as the documentation says: short content columns.

Always
The column will always stretch, irrespective of the Stretch Report Setting set by the End User.


Results can look slightly different:


Imho: Small enhancement with hugh impact.

Modal Dialogs Part 1 - Intro

$
0
0
In this 5 part series of blog posts I will share my understanding of how you can open modal dialogs with dynamic values.

First of all: Why would you need that?
There are particular business cases where your modal dialog (page 2) uses data from the parent page (page 1) which was created during runtime.
For example: Map Selector
I want to select a certain position from a map. The map is called by a button and displayed via a modal dialog. The parameter for the default position are defined on the parent page. After I have chosen a particular position on the map the Latitude and Longitude parameter should be transmitted back towards the parent page.


In this introduction I will explain how you can do it with "Standard APEX" functionality.
On my main page 10 I have several search items (like City, Street...) , two return items (Lat, Lon) and a button called SEARCH. The button executes a standard "Submit Page".
In the processing area I add a branch "After Submit" which gets executed by the SEARCH button. The branch process will open page 11 and will transmit all search parameters.
Info: This way opening a modal dialog may not work below APEX 18.1


Now the modal dialog will open. On page load a dynamic action gets executed to create the map:

vAddress =
  apex.item('P11_CITY').getValue() + '' +
  apex.item('P11_POSTAL_CODE').getValue() + '' +
  apex.item('P11_STREET').getValue() + '' +
  apex.item('P11_HOUSE_NUMBER').getValue()

vDiameter = apex.item("P11_DIAMETER" ).getValue();

apex.event.trigger(document, 'showOpenLayersMap', [{address: vAddress, diameter:vDiameter}]);void(0);


In that little code snippet above I create a single string "address" and a diameter parameter to pass into the map function. Which is by the way created on page 0 as a custom dynamic action named "showOpenLayersMap".

My showOpenLayersMap function is doing all the magic. In the moment the end user is doing a double click on a certain position inside the map a trigger executes a custom dynamic action on page 11 named "setLatLon".

...
apex.event.trigger(
  document, 
  'setLatLon', 
  [{latitude:coordinatesItems[1], longitude:coordinatesItems[0]}]
);
...



This one fires 3 TRUE actions to return the Latitude and Longitude parameter to page 10:
1. Execute JavaScript Code
apex.item("P11_LON" ).setValue(this.data.longitude);
apex.item("P11_LAT" ).setValue(this.data.latitude);

2. Execute PL/SQL Code
:P10_LON := :P11_LON;
:P10_LAT := :P11_LAT;

3. Execute JavaScript Code
parent.location.reload();

Unfortunately I couldn't find a way to send the items back to the parent page dynamically. I just couldn't react on the "Dialog Closed" event. I think it has to do with the explicit submit. The dynamic action handler has no idea about the modal dialog.



Ps.: A separate blog post about open layers and open street map will come later...

Der Fahrplan bis zur APEX Connect 2019

$
0
0
Im Mai 2017 hatte ich euch gefragt worauf ihr euch am meisten freut was die damals anstehende APEX Connect betraf.
Nun schreiben wir das Jahr 2019 und bis zur Connect stehen noch einige Events vor der Tür, bei denen es neben dem technischen Wissensaustausch vor allem auch um die Vernetzung und das teilen des gemeinsamen Spirits geht.


Daher hier der Fahrplan bis zur APEX Connect 2019

Meetups
Das nächste Meetup findet am 11.03. in München zum Thema "APEX 19.1 New Features" statt.
Die anderen Meetupgruppen befinden sich noch im Winterschlaf. News gibt es aus Dresden, dort wurde eine neue Meetup Gruppe gegründet. Das erste Treffen ist bereits in Planung.
Eine Übersicht aller Meetups findet ihr auf apex.world.
Eure Stadt fehlt? Ihr wollt aber trotzdem eine Gruppe gründen und wisst nicht wie? Schreibt mich einfach an (Twitter, Mail) und ich versuche euch dabei zu unterstützen.


Workshops
Am 18.03. findet ein kostenloser APEX Workshop in Trier statt inklusive Hands On. Beeilt euch die Plätze sind begrenzt.

In Frankfurt gibt es am 13.03 einen kostenlosen Oracle Datenbank-Technologie Tag.

Zwischen dem 09.-10.04 bietet die MT AG einen APEX Migrations-Workshop an. Geeignet für alle die endlich auf APEX 18.2 migrieren möchten.


APEX Competition
Bis zur Connect läuft parallel auch die APEX Competition 2019 bei der es darum geht ein APEX Plugin zu entwickeln. Der Gewinner erhält 850 €.
Die Competition läuft noch bis zum 07. April. Genug Zeit für eurer erstes Plugin, den passenden Einstieg findet ihr übrigens in diesem aufgezeichneten Webinar:
Entwicklung von Plug-ins für Oracle Apex – Kurzer Einstieg


Im Mai findet dann die heiligste aller APEX Veranstaltungen im deutschsprachigen Raum statt:

Die APEX Connect

Am Vortag der Connect dem 06.05. (Montag) wird es ein kostenloses Meetup geben, bei dem bekannte Oracle Core Entwickler die letzten News rund um APEX, PL/SQL und Javascript teilen werden.
Zwischen dem 07.05. und dem 09.05. dreht sich dann alles nur noch um die bekannten 4 Buchstaben 

A..P..E..X..

Highlights rund um das Event werden in den kommenden Wochen über unterschiedliche Blogposts detaillierter kommuniziert, bis dahin schaut euch doch mal die Vortragshighlights und den Bewertungsprozess näher an. Das komplette Programm findet ihr hier:

Mein Highlight dieses Jahr sind definitiv die Beginner Sessions:
Geeignet für Newcomer, Studenten, ehemalige Forms-Entwickler, DBA's und alle die APEX näher kennenlernen möchten.

And the APEX Coin goes to...

$
0
0
And the APEX Coin goes to...



Before I tell you.. Here is the story behind it!

Adrian Png is a Senior APEX developer and he is doing a lot of things for the APEX Community. For that reason he earned the APEX.WORLD Member of the year award.

His last outstanding idea was the APEX Challenge Coin!
In short: You can announce one or more great APEX developers who you think earn an APEX Coin.
But please read the full story here: Announcing the APEX Challenge Coin 


Of course I had to ask Adrian to send me 2 coins.


Unfortunately I know to many great APEX developers only in Germany. How in hell will I ever be able to choose one???

So my choice fell on 2 passionate people doing not only APEX. They lead and support our German APEX Community since years. Here are some examples:
 - APEX Meetup organizers
 - Member of the DOAG development community
 - Supporting the APEX Connect conference

But this is not the main reason!

The main reason is the passionate work in supporting and helping "Woman in Tech" (WIT) to become part of our wonderful APEX community.


And the APEX Coin goes to... Carolin Hagemann and Sabine Heimsath !






P.S.: As you can see on the picture I got 3 coins. So thanks Adrian for giving me one as well. :)

Die inoffizielle APEX Connect Agenda

$
0
0
Noch etwas mehr als einen Monat bis zur APEX Connect 2019 und so langsam steigt bei allen die Vorfreude. Dieses Jahr wird es neben dem voll gepackten offiziellen Programmplan auch die ein oder andere Community Aktivität zu erleben geben.

Aber eins nach dem Anderen. Zunächst mal der Verweis auf das aktuelle Vortragsprogramm:


Am Dienstag den 07.05. ist um 9.00 Uhr der offizielle Startschuss und am 09.05. um 17:00 Uhr beenden wir die Connect mit einer Keynote von Jonathan Lewis.

Davor und dazwischen gibt es aber neben dem offiziellen Programm noch allerhand MEHR zu erleben.

Montag 17-21 Uhr: Meetup in der Uni Bonn (kostenlos)
Montag ab 21 Uhr: Gemeinsames Abendessen + 🍺; Ort - offen - (Selbstzahler)

Dienstag 18-20 Uhr: individuelles Abendbrot in der nähe des Hotels
Dienstag ab 20:30 Uhr: Offene Fragerunde mit dem Oracle Development Team und das Beste aus den letzten Monaten.
Dienstag ab 22 Uhr: Ausklingen an der Bar (Selbstzahler)

Mittwoch 7:30 Uhr: 5k Fun Run
Mittwoch 18:00 Uhr: Cruise Dinner
Mittwoch ab 22 Uhr: Party Open End im Club "N8schicht" (Selbstzahler)

Damit ihr alles Wichtige auch während der Konferenz mit bekommt, meldet euch an Twitter an und folgt diesen beiden Hashtags:
#orclapex #apexconn19


Interactive Grid: After Update Trigger

$
0
0
If you want to run a process after the "Interactive Grid" successfully updated all rows you can achieve this with a dynamic action. This can be necessary if you need to update certain columns calculated over several row in the same table you updated within the grid. Problem is to refresh the related data inside the grid as well.

Example:
You edit 3 rows for column A within your grid.
The grid updates column A row by row.
After that an update process should calculate a new result for column B which includes the data from all those updated 3 rows in column A.

Column A
row 1: 150 
row 2: 200
row 3: 100

Column B includes the sum of  column A
row 1: 450 
row 2: 450 
row 3: 450  

All you need to do is to define your Grid like this:
Interactive Grid > Advanced > Static ID: igYourGrid

Dynamic Action Event: Save [Interactive Grid]
Selection Type: Region
Region: Your Grid
Event Scope: Static

Action: Execute PL/SQL Code
custom_pkg.after_update_process;

Action: Execute Javascript Code
var model = apex.region("igYourGrid").widget().interactiveGrid("getViews").grid.model;
model.fetchRecords(model._data);




Be aware that the custom_pkg.after_update_process; process may not be executed when JavaScript errors occur.

This solution is completely dynamic and does not require a page submit.

--
--

Tip of the day
Since all conferences get cancelled worldwide join those online conferences instead:
ACE's at Home: https://itoug.it/aces-at-home/ (March 31th '20)
APEX@Home: https://asktom.oracle.com/pls/apex/f?p=100:551:::NO:551:P551_CLASS_ID:744 (April 16th '20)


Interactive Grid: Validation - Check for duplicated column entries over all rows

$
0
0
I had this situation now a few times and was always to lazy to write it down. :/

During my last task within the fabe project I hat to create a validation to check for duplicated entries inside an Interactive Grid.
Whenever I add "None of the above" twice, an error should occur:
This blog post from Lino Schilde was a good start for my final solution:

Interactive Grid Validation
Validation of Type: PL/SQL Function (returning Error Text)
Code:
declare
  v_cnt number;
begin
 
-- check only if insert or update (not delete "D")
if :APEX$ROW_STATUS in ('C','U') then

  -- select only if the current row is set to Y
  -- positive result if one answer was set to Y
  select max(case when none_yn = 'Y' then 1 else 0 end)
  into v_cnt
  from answer
  where question_id = :P301_QUESTION_ID
  and answer_id != nvl(:ANSWER_ID,0)
  and :NONE_YN = 'Y';
 
  if v_cnt = 1 then
    return 'Another answer was already set up with "None of the above". You need to change and save it first.';
  end if;

end if;
end;

My solution used a "max" aggregation within a "case when" trick to get the right result.

--
--

Tip of the day
All those meeting clients (Skype, Zoom, Hangouts ...) can also be used for APEX friends and beer meetups. #beer #orclapex

utPLSQL - Example Package

$
0
0
A few months ago I got an awesome task to do inside the fabe project:
"Create an utPLSQL test package for the app authentication package"

Well, I never used utPLSQL before and (due to some hangovers at conferences 🥴) I could never really get a heads up towards that topic. Luckily I know one of the main developers Samuel Nitsche. He claims to be a Sith Lord, but for me, he is a true Jedi.
Anyway, I used the force to get him to help me getting a foot into that topic.

First, he gave me some resources.

Documentation:
http://utplsql.org/utPLSQL/latest/

My personal favorite was this article he wrote for the DOAG magazine:
https://cleandatabase.wordpress.com/2019/12/18/but-that-worked-yesterday-or-why-you-should-test-with-utplsql/
(German version: https://www.doag.org/formes/pubfiles/11888853/06_2019-Red_Stack-Samuel_Nitsche-Aber_das_hat_gestern_noch_funktioniert_Testing_mit_utPLSQL.pdf)

A slide that explains the basic usage:


After a little private introduction I made my first attempt and had a discussion with the other Fabe team members about the code usage and naming rules. Well naming rules sounds harsh and we had a long discussion on the topic. Why? Because it is a damn important thing with several long-term consequences. Finally we as a team decided to do it like this:

Test package name should be the same as the original package with an additional "test_" prefix.
For the AUTHENTICATION_PKG the corresponding utPLSQL package was called: TEST_AUTHENTICATION_PKG.

More important were the names for the procedures:
They should be as descriptive as possible. Luckily, fabe runs on  Oracle 19c and we don't have  a 30 character limitation.
For example:
  try_to_hijack_another_users_automatic_login
  send_a_mail_with_the_request_to_reset_the_password

The big advantage - compared to having test methods named after the test methods - is that you focus on the task / behavior and you can have as many test cases for one single procedure as necessary.

So lets get to the code..

The package header looked like this:
Info: This code piece does not include the complete test case for my package.
create or replace package test_authentication_pkg is
-- %suite(Testing authentication logic)
-- %suitepath(Authentication)
-- %rollback(manual)

-- run code:
-- select * from table(ut.run('test_authentication_pkg'));

-- %beforeall
procedure generate_a_valid_apex_session;

-- %beforeeach
procedure create_a_test_user_in_user_profile;

-- %context(Running side programms)
-- %name(1_simple_procedures)

-- %test(generate a hash value and check the output)
procedure generate_a_hash_value_and_check_the_output;

-- %test(check the email format of the test_user)
procedure check_email_format_of_the_test_user;

-- %endcontext

-- %context(Authentication checks)
-- %name(2_login_procedures)

-- %test(verify a positive authentication)
procedure verify_a_positive_authentication;

-- %test(verify an authentication failure)
procedure verify_an_authentication_failure;

-- %test(generate a user login token)
procedure generate_a_user_login_token;

-- %endcontext

-- %aftereach
procedure remove_test_user;

end test_authentication_pkg;

I used a beforeall as well as a beforeeach process to setup my test case.
By using those annotations the procedures run automatically before the test cases (once / always).

The beforeall process created a valid APEX session which required a commit inside my test procedure. For that fact I had to manually clean up all test content and I had to use %rollback(manual)to get the package running.

I also used %context to be able to better organize my procedures.

Be aware that utPLSQL runs procedures not in a certain way as you defined it. That is the reason why I always recreate the test user and cleanup all data after each test procedure.
utPLSQL is intended to work like that. To check each test case independent from another one.

My package body looked like this.
create or replace package body test_authentication_pkg is
-- global variables
gc_user_id constant number := -1;
gc_app_user constant varchar2(100) := 'uttest.authentication_pkg@fab.earth';
gc_salt constant varchar2(100) := 'XYZ1235';
gc_username constant varchar2(100) := 'uttest';
gc_password constant varchar2(100) := 'ThisIsAValidTest';
gc_password_hash constant varchar2(100) := 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

gc_creation_date constant date := to_date(to_char(localtimestamp,'dd.mm.yyyy') || ' 00:01', 'dd.mm.yyyy hh24:mi');


--
-- startup once before all procedures
--
procedure generate_a_valid_apex_session as
l_user_id_in_session_state number;
begin
remove_test_user;

apex_session.create_session(
p_app_id => 100,
p_page_id => 1,
p_username => gc_app_user
);

APEX_UTIL.SET_SESSION_STATE (
P_NAME => 'G_USER_ID',
P_VALUE => gc_user_id
);

l_user_id_in_session_state := APEX_UTIL.GET_SESSION_STATE (
p_item => 'G_USER_ID'
);

--check
ut.expect(l_user_id_in_session_state)
.to_equal(gc_user_id);

end generate_a_valid_apex_session;


--
-- preparation: insert into user_table
--
procedure create_a_test_user_in_user_table as

-- populate expected
l_user_id_entries number;

begin

INSERT INTO user_table(
user_id,
app_user,
active,
username,
first_name,
last_name,
bio,
timezone_id,
country_id,
salt,
password
)VALUES(
gc_user_id,
gc_app_user,
'Y',
gc_username,
'UT',
'Test',
'Internal test account which should not exist regularly.',
168, -- Berlin
86, -- Germany
gc_salt,
gc_password_hash --'ThisIsAValidTest'
);

select count(*)
into l_user_id_entries
from user_table
where user_id = gc_user_id;

--check
ut.expect(l_user_id_entries).to_equal(1);

end create_a_test_user_in_user_table;

--
-- cleanup from user_table
--
procedure remove_test_user as

-- populate expected
l_user_id_entries number;

begin

delete from user_pw_table
where email_address = gc_app_user;

delete
from user_log_table
where user_id = gc_user_id;

delete
from user_table
where user_id = gc_user_id;

end remove_test_user;


--
--
-- Simple procedure checks
--
--

PROCEDURE generate_a_hash_value_and_check_the_output IS
BEGIN
ut.expect(authentication_pkg.cust_hash(gc_app_user,gc_salt,gc_password))
.to_equal(gc_password_hash);
END;


PROCEDURE check_email_format_the_test_user IS
BEGIN
ut.expect(authentication_pkg.email_format(p_email => gc_app_user))
.to_be_true();
END;


--
--
-- Login tests
--
--

PROCEDURE verify_a_positive_authentication IS
BEGIN
ut.expect(authentication_pkg.custom_authenticate(gc_app_user,gc_password))
.to_be_true();
END;

PROCEDURE verify_an_authentication_failure IS
BEGIN
ut.expect(authentication_pkg.custom_authenticate(gc_app_user,gc_password||'X'))
.to_be_false();
END;


-- Create an user login entry / token and create JWT SSO Token as cookie
PROCEDURE generate_a_user_login_token IS
l_actual number;

l_owautil_var owa.vc_arr;
l_owautil_val owa.vc_arr;
BEGIN

-- Pre Test configuration
-- intialize owautil for the generate_fingerprint procedure
-- to prevent ORA-06502, ORA-06512: at "SYS.OWA_UTIL", line 354
l_owautil_var(1) := 'HTTP_USER_AGENT';
l_owautil_val(1) := 'Windows 10 Client with Firefox xxx.x';
owa.init_cgi_env( l_owautil_var.count, l_owautil_var, l_owautil_val );

-- start test case
authentication_pkg.generate_user_log (
p_user_id => gc_user_id
);

-- verifiy user_log_table entry
select count(*)
into l_actual
from user_log_table
where user_id = gc_user_id;

ut.expect(l_actual).to_equal(1);

-- remove test data
delete from user_log_table
where user_id = gc_user_id;

END generate_a_user_login_token;

end test_authentication_pkg;

Now there is a lot to say about that package body let me start with the obvious.

1. I used global variables for all of the values my  test case procedures rely on.
Advantage: Saves time when I need to adjust the parameters. It is also more readable by using understandable variable names.

2. Use the API and keep the test case simple
Use the predefined API procedures.
ut.expect(...).to_be_true();
ut.expect(...).to_equal(1);
ut.expect(...).to_equal(gc_user_id);

A common way writing the test cases are looking similar to this example:
procedure test_action as
l_actual number;
-- populate expected
l_expected number := 1;

begin
l_actual := authentication_pkg.check_something(gc_app_user);
ut.expect(l_actual).to_equal(l_expected);
end test_action;
I would recommend to do it like this:
procedure test_action as
begin
ut.expect(authentication_pkg.check_something(gc_app_user))
.to_equal(1);
end test_action;

Advantage:
Use the proper variable names instead of generic ones.
By splitting the procedure call and the result check in two lines. We can read it much better.
By adding the target procedure inside ut_expect, we spare an additional variable, create almost zero code and still keep the test readable.
Thanks to Samuel for showing me to code like this.

4. Use a custom APEX session procedure
Advantage: Independent code pieces like this should be extracted into separate procedures (separation of concerns). In that case you can focus on the real test case errors.

5.  Define your cleanup process
In my example remove_test_user I manually removed all data because in the original package I had some commits inside my called procedures. If you don't have that situation, instead you can  define a simple rollback process.
procedure remove_test_user as

begin
rollback;

end remove_test_user;

6. Error ORA-06502, ORA-06512: at "SYS.OWA_UTIL"
      l_owautil_var(1) := 'HTTP_USER_AGENT';
      l_owautil_val(1) := 'Windows 10 Client with Firefox xxx.x';     
      owa.init_cgi_env( l_owautil_var.count, l_owautil_var, l_owautil_val );

Why? You need to pretend to be a client. :) Otherwise it will be null.

The result for the real procedure looks like this:
authentication
  Testing authentication logic
    Password checks
      reset the test user password [,041 sec]
      send a mail with the request to reset the password [,107 sec]
      verify the requested password activity [,025 sec]
      verify the signup password [,019 sec]
    Authentication checks
      verify a positive authentication [,055 sec]
      verify an authentication failure [,069 sec]
      generate a user login token [,084 sec]
      automatically recreate an apex session for the test user [,26 sec]
      try to hijack another users automatic login [,238 sec]
    Running side programms
      generate a hash value and check the output [,053 sec]
      check the email format from the test_user [,036 sec]

Finished in 1,076257 seconds
11 tests, 0 failed, 0 errored, 0 disabled, 0 warning(s)



Thanks again Samuel, Hayden and the whole Fabe team making this blog post possible.
It showed me once more what community spirit really meant.


APEX CONNECT 2020 [ONLINE]

$
0
0
You may already noticed..
Anyway I'm glad to remind you that next week a full two day APEX online conference will be held:

APEX CONNECT 2020 [ONLINE]


We will have three parallel tracks divided in different topics and languages:
  • Track 1: APEX [🇩🇪]
  • Track 2: PL/SQL [🇩🇪]
  • Track 3: APEX and PL/SQL [🇬🇧]
Register now: https://apex.doag.org/en/home#c40443

We also plan a beer session on Tuesday evening (CET) so stay tuned on Twitter: #apexconn20

This is not all..
On Monday there will be an ACEs at Home APEX day including a nice presentation from my side:
Fighting climate change with Oracle - a worldwide initiative