Actions

Actions are used to run stored procedures after some event occurs, like editing cell, row insert or manual action selection. To add an action in workspaces you need to complete the following steps:

  1. Configure the control table de_action_sps
  2. If you want the action to be available in View Settings like in Example A, then you need to add the action in de_actions.
  3. Configure the control table data_element_actions if the action should run after the standard update action
  4. Set up the stored procedure in the database
  5. Configure the client config depending on the type of action. Read more about client config [here](/6.2/technical/core/workspace-client-config/#actions

de_action_sps

In this control table we configure the database actions to be able to use them in workspace actions, inline actions, insert actions or update actions.

  • id (int) - required: The id of the action you are adding, should be within your selected id range
  • name (nvarchar) - required: The name you give your action, must be unique
  • caption (nvarchar) - required: The name that is shown in the system. You can either hard code the caption or enter a translation key that references the translations in the system
  • description (nvarchar) - not required: shows up in the system when you use workspace actions. Always good to populate this with a clear description for documentation purposes.
  • sp_schema (nvarchar) - required: The database schema that the store procedure belongs to.
  • sp_name (nvarchar) - required: The name of the stored procedure in the database.

de_actions

The de_actions table contains the Actions that show up within the AGR View settings in the system and can be added into a view.

  • [id] (int): the id of the action, must be unique
  • [name]: the name you give the action
  • [caption] (nvarchar): The name that is shown in the system. You can either hard code the caption or enter a translation key that references the translations in the system
  • [description] (nvarchar) - not required: appears when user clicks the question mark in View Settings in the system
  • [type] (nvarchar) - not required: probably not used in the code, is always populated with null
  • [group_id] (int): (required): The id of the group you want to put your action. The group_id references the table data_element_groups. The group you select must be connected to the workspace type so that the action will appear in the system
  • [action_json] (nvarchar) - required: Action settings are defined in this column in JSON format. Information about available properties are shown below and it can also be useful to look at the example actions provided in the database.
  • [available] (int): 1 if the action should be available in the system; 0 otherwise
  • [selected_by_default]): 1 if the action should be selected into the grid settings by default, 0 otherwise

Workspace Actions are stored in dbo.de_actions

actions

Action settings is defined in dbo.de_actions.action_json:

  • clientData (object): …
    • gridSelection (string) ‘all’: … [The default value is ‘all’ but this parameter can’t be omitted 🤔]
    • actionArgs: (object): Arguments to pass to stored procedure.
      • includeAffected (boolean): …
      • longRunningAction (boolean): Start the action without waiting for it to complete (the API will not wait).
      • refreshGridAfterAction (boolean): Sync grid data when this action finishes. Note: There is a known issue in the app that causes the drill-down to load incorrectly if refreshGridAfterAction and transferView is used at the same time. See transferView.
      • serieOverrideFromArg (string): Matches name in modal operation selected options field (operations.fields.name)
      • serieOverride ???
  • operations (array of objects): …
    • name (string): …

    • type (“modal” | “lockAffectedCells” | “sp” | “refreshAffectedCells” | “unlockAffectedCells” | “refreshView”):

    • fields (array of objects): (Only if the type = "modal") Form fields in modal. Contains an array for all of the fields that should be presented to the user in a modal when the action is executed. Each object describes one field and will result in the user selected value to be sent to the server for the action of type “sp”.

      • name (string): Used to create form object in code. Must be unique in this form.
      • caption (string): Field title.
      • description (string): Optional. Will add a (?) icon after caption that user can click and see description.
      • type (“select” | “text” | “int” | “string” | “date”): Type of input element in form. E.g. use “select” to get dropdown.
      • required (boolean): Required fields must be set before modal is submitted.
      • defaultValue (string): Add default value to input.
      • optionValues (array of objects): (Only used if type="select"). Options to show in dropdown. name (string): The value set when option is selected. caption (string): The option title displayed in dropdown.
      • optionsFrom (“viewDataSeries” | “planDataSeries”): (Only used if type="select"). The options in the select should consists of:
        • When “viewDataSeries”: The dataseries that are present in the current view
        • When “planDataSeries”: The dataseries that are present in the current workspace.
      • multiple (boolean): (Only used if type="select"). Whether the select should have multiple selections possible. When false (default) only one option can be selected. When true more than one option can be selected. In this case, when the action is executed, the value sent to the server will be an array of selected name values (instead of just the selected name value).
      • optionsQuery (object with three values): (Only used if type="select"). Specifies that the option values should be acquired by a query executed when the modal box is presented to the user. The values that are required are the following:
        • nameColumn (string): The name of the column in the query result that provides the name value of options
        • captionColumn (string): The name of the column in the query result that provides the caption value of options
        • query (DeQuery): The query to be executed to get the option values for the select.

Example:

Note: When type is set to "date" the date value is delivered to the backend as json. To get the value into a TSQL date value the following is needed

  DECLARE @date_val date, @date_raw nvarchar(max)
  SET @date_raw = dbo.fn_de_act_get_arg(@query, 'dateField')
  SELECT @date_val = DATEFROMPARTS(year_val, month_val, day_val)
    FROM OPENJSON(JSON_QUERY(@date_raw))
	WITH ( year_val int '$.year', month_val int '$.month', day_val int '$.day' )
{
    "clientData": {
        "gridSelection": "all",
        "actionArgs": {
            "refreshGridAfterAction": true,
            "includeAffected": true,
            "serieOverride": {
                "readFromArg": "viewSerie"
            }
        }
    },
    "operations": [
        {
            "type": "modal",
            "fields": [
                {
                    "name": "viewSerie",
                    "caption": "View serie",
                    "type": "select",
                    "optionValues": "viewDataSeries",
                    "description": "Selection of any of the data series visible in the view"
                },
                {
                    "name": "planSerie",
                    "caption": "Plan serie",
                    "type": "select",
                    "optionValues": "planDataSeries",
                    "description": "Selection of any of the data series in the plan",
                    "required": true
                },
                {
                    "name": "customOptions",
                    "caption": "A custom list of options",
                    "type": "select",
                    "optionValues": [
                        {
                            "name": "val1",
                            "caption": "Value 1"
                        },
                        {
                            "name": "val2",
                            "caption": "Value 2"
                        },
                        {
                            "name": "val3",
                            "caption": "Value 3"
                        }
                    ],
                    "defaultValue": "val2",
                    "description": "Another long description of this field"
                },
                {
                    "name": "intField",
                    "caption": "Running time (sec)",
                    "type": "int",
                    "description": "This is an integer value"
                },
                {
                    "name": "stringField",
                    "caption": "String Value",
                    "type": "string",
                    "description": "This is a string value"
                }
            ]
        },
        {
            "type": "sp",
            "name": "test"
        }
    ]
}

data_element_actions

This table is configured when we want a custom procedure to run after the standard update procedure. From version 6.2 this table is only used for column update, series update is configured through the data_element_series table

  • action_name (nvarchar) - required: The name of the action this execution will follow, usually “update_action”
  • data_element_type (nvarchar) - required: series or column, depending on weather this should run after a column update or series update
  • data_element_name (nvarchar) - required: The name of the data_element or the data_element_ref_column that is being updated
  • de_action_sp_name (nvarchar) - not required: The name of the action sps, from de_action_sps
  • ord (int) - required: If you configure many actions to run after the update, here you define the order they should run in

Setting up the stored procedure

The procedure that is run as an action needs to have the required input parameters and it needs to return some output. The @query is sent to the procedure by the system and contains the JSON XXXX needed to know the scope the action should be run for. The procedure needs to start by parsing the JSON into variable that can be used in the database. THis is always done in the same way so use one of the example procedurse below when creating your action.

In this chapter you can find a few examples of stored procedures that can be used in different scenarios in the system.

Arguments in Stored Procedure

The arguments specified in actionArgs will be available in the @query parameter in the stored procedure. For example, to pass a specific integer value from action to the store procedure:

"actionArgs": { "intValue": 42 }

Now the stored procedure one can access the value with:

DECLARE @int_value INT
SET @int_value = CONVERT(INT, dbo.fn_de_act_get_arg(@query, 'intValue'))

Refreshing Row After Action Execution

The backend store procedure executed by the action can provide a refreshed row values. To do this it must return a "data" property with the value ["refreshedRow"] and provide a second result set containing the refreshed values. An example of this would be:

SELECT '{"result": "OK", "caption": "Everything executed fine!", "data": ["refreshedRow"]}' AS result
	SELECT 'Some new value' AS item_group_lvl_1, 'Also new' AS item_group_lvl_2

Removing row after action execution

It is also possible to instruct the client to delete the row in the grid, typically used for delete actions. This is an example of this:

SELECT '{"result": "OK", "caption": "deleteRow test", "deleteRow": true}' as result, 1 as deleteRow

Transferring to another view after action execution

It is also possible to instruct the client to transfer to another view when the action is executed. Note: If transferView is used, the refreshGridAfterAction property in de_actions.action_json.clientData.actionArgs should be false. Otherwise the drill-down can load incorrectly.

Example:

SELECT '{"result": "OK", "caption": "deleteRow test", "transferView": "name-of-view-to-transfer-to"}' as result, 1 as deleteRow

Transferring to another view after action execution and setting drilldowns on that view

In addition to transferring to another view, the drilldowns on that view can also be set by including them in the result. This is an example of how to do that:

SELECT '{"result": "OK", "caption": "deleteRow test", "transferView": "name-of-view-to-transfer-to", "drilldowns":{"location": 2, "some_other_drilldown": 666}}' as result, 1 as deleteRow

Example A: Workspace Actions triggered manually in the system

When the user triggers this action from the User Interface the procedure should run only for the items (scope) that the user has filtered on in the workspace. For example if a user has filtered on 3 item numbers running the action should only affect these 3 item numbers

  1. Create the procedure (example provided below) in the database
  2. Add it into dbo.de_action_sps and then add it to dbo.de_actions
  3. Go into the system and find the action and add it in grid settings actions
  4. Run the action – at this point it shouldn‘t do anything
  5. Catch the action query in extended events in the database
  6. Copy the query from the action in extended events and use it to debug your procedure.
  7. Run the procedure step by step and read the comments, recommend commenting out when it is suggested to get a feeling what is going on.
  8. Finally make the changes you want at the end of the procedure.
  9. Alter the procedure and try running the action again in the system. Now something should have updated.
CREATE PROCEDURE [dbo].[de_act_example_a]
-- the following parameters must be included when creating all 'workspace' actions that are used in dbo.de_action_sps
(
    @query NVARCHAR(MAX),
    @user_id INT,
	@debug BIT = 0
)
AS
BEGIN

	SET NOCOUNT ON
	BEGIN TRY

	-- Uncomment this below for debugging and comment out all above. Remember you must use the query from your action
	-- which you find by running your action in the system and catching the action from watching live data in Extended Events

	--DECLARE @query NVARCHAR(MAX) = N'{"args":{"includeAffected":true,"serieOverride":{"readFromArg":"copyTo"},"copyFrom":"forecast","copyTo":"planner_baseline","displaySuccessfulNotification":true},"query":{"filters":[{"column":{"name":"date_month_id"},"values":[-6,12],"operator":"RELPERIODS","ui":{"selectedSubtypeId":"caption_month_short","column":{"name":"period"},"fixed":false}},{"column":{"name":"location_id"},"operator":"=","values":10}],"columns":[{"name":"item_no"},{"name":"primary_vendor"}],"series":[{"name":"planner_baseline"},{"name":"planner_promotions"},{"name":"demand"},{"name":"sale"}],"pivot":{"column":"caption_month_short","paging":{"offset":0,"limit":1000}}}}',
	--@user_id INT = 1,
	--@debug BIT = 0

-- declaring some variables and then parsing the query string
	DECLARE @series de_qb_data_series,
	@filters de_qb_filters, @columns de_qb_columns, @table_joins NVARCHAR(MAX),  @where_filter NVARCHAR(MAX), @sql NVARCHAR(MAX)



	-- only for debugging
    DECLARE @msg NVARCHAR(500)
    IF @debug = 1 PRINT 'VALIDATING JSON:'
	IF ISJSON(@query) = 0
	BEGIN
		SET @msg = 'Invalid JSON request'
		SELECT dbo.json_error(@msg) as result
		EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
		RETURN
	END

	-- I recommend that you comment out the select statements below each parsing part to see what is stored in the @filters/@seris/@columns
	-- variables then you can see that all the filters/drilldowns/series/columns from your view in the workspace are stored in the variables.

	-- parsing filters
	INSERT INTO @filters
	SELECT * FROM [dbo].[fn_de_qp_filters](JSON_QUERY(@query, '$.query'))
	--SELECT * FROM @filters

	-- parsing series
	INSERT INTO @series
	SELECT * FROM [dbo].[fn_de_qp_series](JSON_QUERY(@query, '$.query'))
	--SELECT * FROM @series

	--parsing columns
	INSERT INTO @columns
	SELECT * FROM [dbo].[fn_de_qp_columns](JSON_QUERY(@query, '$.query'))
	--SELECT * FROM @columns

	--now let's get all the joins and filters on string format. You can comment out the select statements (@table_joins/@where_filter)
	-- to see what is in the string variables

	EXEC [de_qb_sql_table_joins] @columns,NULL,@filters,@user_id, @table_joins OUT

	--SELECT @table_joins

	EXEC [dbo].[de_qb_sql_where_filter_clause] @filters, 1, @where_filter OUT

	--SELECT @where_filter


	-- YOU SHOULD ONLY NEED TO ADJUST/CHANGE THE CODE BELOW THIS COMMENT

	-- Finally the parsed data must be combined and I recommend using temp table which stores all the data in the view/grid
	-- and then you can do whatever you want to do/update regarding the data.

	--creating temp table that in my case I call update_table. Adjust the name and columns to your need based on what is in your view/grid
	IF OBJECT_ID('tempdb..#update_table') IS NOT NULL
	DROP TABLE #update_table
	CREATE TABLE #update_table([item_id] INT,
							   [item_no] NVARCHAR(MAX),
							   [location] NVARCHAR(100),
							   [primary_vendor] NVARCHAR(100),
							   [first_date_id_of_month] INT)


	-- Now I use the sql string to insert into the temp table. Here you have to change the columns you are inserting into based on the columns
	-- in the table you created above as well as selecting the right columns from right tables.
	-- The table names always start with 'DET' and then the id of the table in data_element_ref_tables. For example the items table has id=1 in
	-- dbo.data_element_ref_tables which means that all the columns you want and need from the items table come from DET1 e.g. DET1.id is
	-- the item_id

	SELECT @sql = 'INSERT INTO #update_table ([item_id], [item_no], [location], [primary_vendor], [first_date_id_of_month])
				   SELECT DISTINCT DET1.id, DET2.item_no, DET4.name, DET5.name, DET0.first_date_id_of_month' -- change this row and the row above
				   + CHAR(10) + @table_joins + CHAR(10) + CASE WHEN LEN(@where_filter) = 0 THEN '' ELSE @where_filter END -- should not need to change this row

	-- comment out the statement below to see your complete string and try running it if you want
	--PRINT @sql

	EXEC sp_executesql @sql -- this will run what is in the sql string. So in this case we are inserting into the update_table

	-- comment out the statement below to see that you have all the required data from your view/grid now stored in a temp table
	--SELECT * FROM #update_table


	-- do whatever you need to do (update values, insert new rows, run some procedures) below by
	-- joining with/using the data in the temp table you created above.
	----------------------------------------------------------------------


	----------------------------------------------------------------------
	SELECT dbo.json_ok('Everything executed fine!') as result  -- the procedure must return a result, like this. You can change the result message if you want.

    END TRY
    BEGIN CATCH
		    DECLARE @sp_name NVARCHAR(500)
		    SELECT @sp_name = s.[name]+'.'+OBJECT_NAME(@@PROCID), @msg = 'Action could not be executed:'+ERROR_MESSAGE()
		    FROM sys.procedures p INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
		    WHERE OBJECT_ID = @@PROCID
            EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
		    SELECT dbo.json_error('Action could not be executed') AS result
    END CATCH;
END
GO

Example B: Workspace Actions triggered manually in the system with input parameters

When the user triggers this action from the client the procedure should run only for the items (scope) that the user has filtered on in the workspace. For example if a user has filtered on 3 item numbers running the action should only affect these 3 item numbers. In addition, in this scenario, we want the user to be able to select one of three options (A,B or C) before the action is run.

  1. Create the procedure (example provided below) in the database
  2. Add it into dbo.de_action_sps and then add it to dbo.de_actions For this scenario the following config can be inserted into the de_actions table
 {"clientData":{
      "gridSelection":"all",
      "actionArgs":{
         "includeAffected":true,
         "serieOverride":{
            "readFromArg":"viewSerie"
         }
      }
   },
   "operations":[
      {
         "type":"modal",
         "fields":[
            {
               "name":"customOptions",
               "caption":"A custom list of options",
               "type":"select",
               "optionValues":[
                  {
                     "name":"optionA",
                     "caption":"A"
                  },
                  {
                     "name":"optionB",
                     "caption":"B"
                  },
                  {
                     "name":"optionC",
                     "caption":"C"
                  }
               ],
               "defaultValue":"optionA",
               "description":"Another long description of this field"
            }
         ]
      },
      {
         "type":"sp",
         "name":"update_sales_plan_by_locations"
      }
   ]
}
  1. Go into the system and find the action and add it in grid settings actions
  2. Run the action – at this point it shouldn‘t do anything
  3. Catch the action query in extended events in the database
  4. Copy the query from the action in extended events and use it to debug your procedure.
  5. Run the procedure step by step and read the comments, recommend commenting out when it is suggested to get a feeling what is going on.
  6. Finally make the changes you want at the end of the procedure.
  7. Alter the procedure and try running the action again in the system. Now something should have updated.
CREATE PROCEDURE [dbo].[de_act_example_b]
-- the following parameters must be included when creating all 'workspace' actions that are used in dbo.de_action_sps
(
    @query NVARCHAR(MAX),
    @user_id INT,
	@debug BIT = 0
)
AS
BEGIN

	SET NOCOUNT ON
	BEGIN TRY

	-- Uncomment this below for debugging and comment out all above. Remember you must use the query from your action 
	-- which you find by running your action in the system and catching the action from watching live data in Extended Events

	--DECLARE @query NVARCHAR(MAX) = N'{"args":{"includeAffected":true,"serieOverride":{"readFromArg":"viewSerie"},"customOptions":"optionA","displaySuccessfulNotification":true},"query":{"filters":[{"column":{"name":"date_month_id"},"values":[1,12],"operator":"RELPERIODS","ui":{"selectedSubtypeId":"caption_month_short","column":{"name":"period"},"fixed":false,"class":"Oe"}},{"column":{"name":"location_id"},"operator":"=","values":[10]}],"columns":[],"series":[{"name":"planner_baseline"},{"name":"planner_promotions"},{"name":"demand"},{"name":"sale"}],"pivot":{"column":"caption_month_short","paging":{"offset":0,"limit":1000}}}}',
	--@user_id INT = 1,
	--@debug BIT = 0

-- declaring some variables and then parsing the query string
	DECLARE  @series de_qb_data_series,
	@filters de_qb_filters, @columns de_qb_columns, @table_joins NVARCHAR(MAX),  @where_filter NVARCHAR(MAX), @sql NVARCHAR(MAX)


	-- only for debugging
    DECLARE @msg NVARCHAR(500)
    IF @debug = 1 PRINT 'VALIDATING JSON:'
	IF ISJSON(@query) = 0
	BEGIN
		SET @msg = 'Invalid JSON request'
		SELECT dbo.json_error(@msg) as result
		EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
		RETURN
	END


	-- I recommend that you comment out the select statements below each parsing part to see what is stored in the @filters/@seris/@columns 
	-- variables then you can see that all the filters/drilldowns/series/columns from your view in the workspace are stored in the variables.

	-- parsing filters
	INSERT INTO @filters
	SELECT * FROM [dbo].[fn_de_qp_filters](JSON_QUERY(@query, '$.query'))
	--SELECT * FROM @filters

	-- parsing series
	INSERT INTO @series
	SELECT * FROM [dbo].[fn_de_qp_series](JSON_QUERY(@query, '$.query'))
	--SELECT * FROM @series

	--parsing columns
	INSERT INTO @columns
	SELECT * FROM [dbo].[fn_de_qp_columns](JSON_QUERY(@query, '$.query'))
	--SELECT * FROM @columns

	--now let's get all the joins and filters on string format. You can comment out the select statements (@table_joins/@where_filter) 
	-- to see what is in the string variables

	EXEC [de_qb_sql_table_joins] @columns,NULL,@filters,@user_id, @table_joins OUT

	--SELECT @table_joins

	EXEC [dbo].[de_qb_sql_where_filter_clause] @filters, 1, @where_filter OUT

	--SELECT @where_filter

	-- YOU SHOULD ONLY NEED TO ADJUST/CHANGE THE CODE BELOW THIS COMMENT

	----------------------------------------------------------------------------------------------
	-- HERE YOU HAVE TO DO DO CHANGES RELATED TO THE PARAMETERS IN YOUR ACTION

	-- Declaring variable that will store the option chosen in the action (A,B,C in my case)
	-- If your action has int/string variables and more than one parameter you can declare your variables here for that
	DECLARE @option NVARCHAR(MAX)

	-- Here I'm looking at the arguments from the query and storing the option chosen into the @option variable
	-- Example: if the user has chosen option A when running the action, the @option variable would be 'A'
	-- If you have other types of parameters I recommend that you run the following query: SELECT * FROM @q_args
	-- There you should see your parameters and in that way figure out how to catch them

    SET @option = JSON_VALUE(@query, '$.args.customOptions')

	--------------------------------------------------------------------------------------------------------


	-- Finally the parsed data must be combined and I recommend using temp table which stores all the data in the view/grid 
	-- and then you can do whatever you want to do/update regarding the data.

	--creating temp table that in my case I call item_list. Adjust the name and columns to your need based on what is in your view/grid
	IF OBJECT_ID('tempdb..#item_list') IS NOT NULL
	DROP TABLE #item_list
	CREATE TABLE #item_list([item_id] INT)


	-- Now I use the procedure [dbo].[de_qb_sql_specific_columns_select] to give me the column item_id (dbo.data_element_ref_columns) using the @query and insert into the ids into the temp table. Here you have to change the columns you are inserting into based on the columns
	-- in the table you created.

    EXEC dbo.de_qb_sql_specific_columns_select @query=@query, @user_id= @user_id, @debug = @debug, @columns_comma_list = 'item_id', @distinct = 1, @sql = @sql OUT

    SET @sql = 'INSERT INTO #item_list (item_id) ' + @sql + ';'

	-- comment out the statement below to see your complete string and try running it if you want
	PRINT @sql
	
	EXEC sp_executesql @sql -- this will run what is in the sql string. So in this case we are inserting into the item_list

	-- comment out the statement below to see that you have all the required data from your view/grid now stored in a temp table
	--SELECT * FROM #item_list


	-- do whatever you need to do (update values, insert new rows, run some procedures) below by 
	--joining with/using the data in the temp table you created above.
	----------------------------------------------------------------------
	-- Uncomment below if you are using the parameters from the action


	--IF @option = 'A'
	--BEGIN
	--	-- do something
	--END

	--IF @option = 'B'
	--BEGIN
	--	-- do something
	--END

	--IF @option = 'C'
	--BEGIN
	--	-- do something
	--END

    -- the procedure must return a result message. Lets have the result message display what option was selected and the number of item_ids behind the grid data.

    DECLARE @result_msg NVARCHAR(MAX)

    SELECT @result_msg = 'item_id count: '+ CAST(COUNT(il.item_id) AS NVARCHAR)+CHAR(10)+'option selected: '+@option
    FROM #item_list il
	----------------------------------------------------------------------
	--SELECT dbo.json_ok('Everything executed fine!') as result  
    SELECT dbo.json_ok(@result_msg) as result 

    END TRY
    BEGIN CATCH
		    DECLARE @sp_name NVARCHAR(500)
		    SELECT @sp_name = s.[name]+'.'+OBJECT_NAME(@@PROCID), @msg = 'Action could not be executed:'+ERROR_MESSAGE()
		    FROM sys.procedures p INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
		    WHERE OBJECT_ID = @@PROCID
            EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
		    SELECT dbo.json_error('Action could not be executed') AS result
    END CATCH;
END
GO

Example C: Update Actions for series editing

When a user edits a value in a data series in the standard setup it is updated through the procedure ‘dbo.de_update_batch_series’ distributing the values either

• Flat if nothing has been entered in or stored in the data series before

• Proportionally based on what was entered in that data series before if anything has been entered.

In this example we want to distribute the value entered on total level in a particular data series proportionally based on another data series. Therefore we will create another action and config the series to run your new action in stead of the standard one. Let‘s say we want to distribute promotions based on the forecast. Currently if you enter in 1000 in promotions on total level for all items it will spread equally (333 for each item) but we want it to distribute based on the forecast proportions like the screenshot below actions

  1. Create the procedure (example provided below) in the database
  2. Add it into dbo.de_action_sps
INSERT INTO dbo.de_action_sps
select 'update_value_example_c','Distribute Promo based on Forecast','','dbo','de_act_example_c'
  1. Update the series config (either the server_config column in dbo.data_element_series table or in the view settings of a workspace in the AGR client). Change the config on your data series and make sure that when the data series is edited your action will run instead of the standard update_value action. The action in the config is the name in the dbo.de_action_sps table
{
  "edit": {
    "enabled": true,
    "action": "update_value_example_c",
  }
}
  1. Debug, read through comments in the example procedure and change the template procedure based on your needs and data series.
  2. Alter the procedure and make sure the action is giving result as expected.
CREATE PROCEDURE [dbo].[de_act_example_c]
-- the following parameters must be included when creating all 'planner' actions that are used in dbo.de_action_sps
(
    @query NVARCHAR(MAX),
    @user_id INT,
	@debug BIT = 0
)
AS
BEGIN

	SET NOCOUNT ON
	BEGIN TRY

	-- Uncomment this below for debugging and comment out all above. Remember you must use the query from your action
	-- which you find by running your action in the system and catching the action from watching live data in Extended Events

	--DECLARE @query NVARCHAR(MAX) = N'{"args":{"includeAffected":true,"serieOverride":{"readFromArg":"copyTo"},"copyFrom":"forecast","copyTo":"planner_baseline","displaySuccessfulNotification":true},"query":{"filters":[{"column":{"name":"date_month_id"},"values":[-6,12],"operator":"RELPERIODS","ui":{"selectedSubtypeId":"caption_month_short","column":{"name":"period"},"fixed":false}},{"column":{"name":"location_id"},"operator":"=","values":10}],"columns":[{"name":"item_no"},{"name":"primary_vendor"}],"series":[{"name":"planner_baseline"},{"name":"planner_promotions"},{"name":"demand"},{"name":"sale"}],"pivot":{"column":"caption_month_short","paging":{"offset":0,"limit":1000}}}}',
	--@user_id INT = 1,
	--@debug BIT = 0

-- declaring some variables and then parsing the query string
	DECLARE  @series de_qb_data_series,
	@filters de_qb_filters, @columns de_qb_columns, @table_joins NVARCHAR(MAX),  @where_filter NVARCHAR(MAX), @sql NVARCHAR(MAX)

	-- only for debugging
    DECLARE @msg NVARCHAR(500)
    IF @debug = 1 PRINT 'VALIDATING JSON:'
	IF ISJSON(@query) = 0
	BEGIN
		SET @msg = 'Invalid JSON request'
		SELECT dbo.json_error(@msg) as result
		EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
		RETURN
	END


	-- the easiest way to use proportion from another data series is to do the following:
	-- 1. copy from the data series you want to use the proportion from into your data series by using copy values
	-- 2. then use the standard update action to spread proportionally.

	-- creating two new variables which we use when we call the copy values action
	DECLARE @new_query NVARCHAR(MAX), @q_arg_new NVARCHAR(MAX)

	-- change the copyFrom and copyTo data series if you are not using forecast and promotions
	SET @q_arg_new = '{"args":{"includeAffected":true,"serieOverride":{"readFromArg":"copyTo"},"copyFrom":"forecast","copyTo":"planner_promotions","displaySuccessfulNotification":true}'

	-- extract only the query part (not the arguments since we are using the arguments in @q_arg_new
	-- uncomment the 'select @new_query' below and compare it with 'select @query' to see the difference

	SELECT @new_query =  JSON_QUERY(@query, '$.query')
	-- select @new_query

	-- combine the arguments and query parts
	SET @new_query = @q_arg_new + ',"query":' + @new_query + '}'

	-- run the copy values action with the new query
	exec de_execute_action @de_action_name=N'copy_serie',@query = @new_query, @user_id = @user_id

	-- finally run the batch update with the original query.
	exec de_execute_action @de_action_name=N'batch_update',@query=@query,@user_id=@user_id



	----------------------------------------------------------------------
	SELECT dbo.json_ok('Everything executed fine!') as result  -- the procedure must return a result, like this. You can change the result message if you want.

    END TRY
    BEGIN CATCH
		    DECLARE @sp_name NVARCHAR(500)
		    SELECT @sp_name = s.[name]+'.'+OBJECT_NAME(@@PROCID), @msg = 'Action could not be executed:'+ERROR_MESSAGE()
		    FROM sys.procedures p INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
		    WHERE OBJECT_ID = @@PROCID
            EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
		    SELECT dbo.json_error('Action could not be executed') AS result
    END CATCH;
END
GO

Example D: Update actions for column editing

When a user edits a value in a column in a workspace in the standard setup it is updated through the procedure ‘dbo.de_update_column’.

This action will make the forecast invalid when a user will edit the ABC column in the system. That is done by running action after the standard update action has updated the ABC column. Therefore we will create another action and config the column to run your new action after the standard one.

Steps

  1. In this example we are working with a workspace/view that could look like this (column only) actions
  2. Make sure the ABC column is editable (see more in Column Client Config)
  3. Create the stored procedure in the database - see example below
  4. Add the action to dbo.de_actions_sps
  5. Since we want the standard update to run in order to update the ABC column and THEN we want to update the valid field in forecast table we must add the action into dbo.data_element_actions table

INSERT INTO data_element_actions (action_name, data_element_type,data_element_name,de_action_sp_name,ord) VALUES (‘update_value’,‘column’,‘abc’,‘de_act_example_d’,1)

  1. Test and validate
CREATE PROCEDURE [dbo].[de_act_example_d]
-- the following parameters must be included when creating all 'planner' actions that are used in dbo.de_action_sps
(
    @query NVARCHAR(MAX),
    @user_id INT,
	@debug BIT = 0
)
AS
BEGIN

	SET NOCOUNT ON
	BEGIN TRY

	-- Uncomment this below for debugging and comment out all above. Remember you must use the query from your action
	-- which you find by running your action in the system and catching the action from watching live data in Extended Events

--	DECLARE @query NVARCHAR(MAX) = N'{"args":{"newValue":"A","oldValue":"C","targetColumn":"abc","clientColumnChanged":"abc"},"query":{"filters":[{"column":{"name":"location_id"},"operator":"=","values":10},{"column":{"name":"master_item_id"},"operator":"=","values":86},{"column":{"name":"master_item_id"},"operator":"=","values":86}],"columns":[{"name":"item_no"},{"name":"name"},{"name":"abc"}],"series":[]}}',
--	@user_id INT = 1,
--	@debug BIT = 0


-- declaring some variables and then parsing the query string
	DECLARE  @series de_qb_data_series,
	@filters de_qb_filters, @columns de_qb_columns, @table_joins NVARCHAR(MAX),  @where_filter NVARCHAR(MAX), @sql NVARCHAR(MAX),@q_createdObj pjsonData


	-- only for debugging
    DECLARE @msg NVARCHAR(500)
    IF @debug = 1 PRINT 'VALIDATING JSON:'
	IF ISJSON(@query) = 0
	BEGIN
		SET @msg = 'Invalid JSON request'
		SELECT dbo.json_error(@msg) as result
		EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
		RETURN
	END



	-- I recommend that you comment out the select queries below each parsing to see what is stored in the variables

	-- parsing filters
--	INSERT INTO @filters
--	SELECT * FROM [dbo].[fn_de_qp_filters](JSON_QUERY(@query, '$.query'))
--	SELECT * FROM @filters

	--parsing columns
--	INSERT INTO @columns
--	SELECT * FROM [dbo].[fn_de_qp_columns](JSON_QUERY(@query, '$.query'))
--	SELECT * FROM @columns

	--creating temp table that in my case I call item_list. Adjust the name and columns to your need based on what is in your view/grid
	IF OBJECT_ID('tempdb..#item_list') IS NOT NULL
	DROP TABLE #item_list
	CREATE TABLE #item_list([item_id] INT)


	-- Now I use the procedure [dbo].[de_qb_sql_specific_columns_select] to give me the column item_id (dbo.data_element_ref_columns) using the @query and insert into the ids into the temp table. Here you have to change the columns you are inserting into based on the columns
	-- in the table you created.

    EXEC dbo.de_qb_sql_specific_columns_select @query=@query, @user_id= @user_id, @debug = @debug, @columns_comma_list = 'item_id', @distinct = 1, @sql = @sql OUT

    SET @sql = 'INSERT INTO #item_list (item_id) ' + @sql + ';'

    EXEC sp_executesql @sql -- this will run what is in the sql string. So in this case we are inserting into the item_list


	-- Finally we update the forecast table, making sure that the valid field is set to 0 for only the item that was updated.


        UPDATE f
        SET f.valid = 0
        FROM dbo.forecasts f
        INNER JOIN #item_list il ON f.item_id = il.item_id


	----------------------------------------------------------------------
	-- refreshing and returning the results
	SELECT '{"result": "OK", "caption": "ABC was updated and Forecast made Invalid"}' AS result


    END TRY
    BEGIN CATCH
		    DECLARE @sp_name NVARCHAR(500)
		    SELECT @sp_name = s.[name]+'.'+OBJECT_NAME(@@PROCID), @msg = 'Action could not be executed:'+ERROR_MESSAGE()
		    FROM sys.procedures p INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
		    WHERE OBJECT_ID = @@PROCID
            EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
		    SELECT dbo.json_error('Action could not be executed') AS result
    END CATCH;
END

Example E: Insert Actions

In this example we want for some reason to be able to add new locations through the system. This can be done with a insert action which will add the new location into the location table.

  1. In the View Settings config the insert functionality so that new rows can be added into the view. A plus will appear in the right upper corner. If the plus (+) is selected a new row will appear which can be confirmed (or discarded) after a name of the location has been entered. Note that the action is referring to the action name in dbo.de_actions_sps table
{
  "insert": {
    "preInsertQuery": {
      "columns": [
        {
          "name": "location"
        }
      ]
    },
  "enabled": true,
  "requiredColumns": [
    "location"
    ],
  "action": "add_location"
},
  "row": {
    "autoRefresh": true,
    "refreshWholeGrid": true
  },
  "name": "new-locations"
}
  1. Create the template procedure in the database (see below) and add the action into dbo.de_actions

  2. Adjust the action procedure for your needs and finally try adding in new locations from the system

CREATE PROCEDURE [dbo].[de_act_example_e]
-- the following parameters must be included when creating all 'workspace' actions that are used in dbo.de_action_sps
(
    @query NVARCHAR(MAX),
    @user_id INT,
	@debug BIT = 0
)
AS
BEGIN

	SET NOCOUNT ON
	BEGIN TRY

	-- Uncomment this below for debugging and comment out all above. Remember you must use the query from your action
	-- which you find by running your action in the system and catching the action from watching live data in Extended Events

--	DECLARE @query NVARCHAR(MAX) = N'{"args":{"createdObj":{"location":"new location","location_id":10,"rowNodeId":23,"inserted":true,"submitting":true,"isFirstLine":true}},"query":{"filters":[],"columns":[{"name":"location"}],"series":[]}}',
--	@user_id INT = 1,
--	@debug BIT = 0


-- declaring some variables and then parsing the query string
	DECLARE  @series de_qb_data_series,
	@filters de_qb_filters, @columns de_qb_columns, @table_joins NVARCHAR(MAX),  @where_filter NVARCHAR(MAX), @sql NVARCHAR(MAX)



	-- only for debugging
    DECLARE @msg NVARCHAR(500)
    IF @debug = 1 PRINT 'VALIDATING JSON:'
	IF ISJSON(@query) = 0
	BEGIN
		SET @msg = 'Invalid JSON request'
		SELECT dbo.json_error(@msg) as result
		EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
		RETURN
	END

	--parsing query

	-- I recommend uncommenting out the select query below this comment. There you will see what you need to catch from the system.
	-- SELECT * FROM @q_createdObj


	-- Declaring variables to store the new location name and the highest location id in the location table
	-- so that we make sure that we add higher location id into the table.
	DECLARE @max_location_id INT,@new_location NVARCHAR(255), @new_location_id INT

	-- storing the new location name that was entered in the system into the @new_location variable.
	SET @new_location = JSON_VALUE(@query, '$.args.createdObj.location')
	-- finding highest location id in the location table
	SET @max_location_id = (SELECT MAX(id) FROM dbo.locations)

	-- The new location id will get one higher integer number than the highest location id
	SET @new_location_id = @max_location_id + 1

	-- Adding the new location into the locations table
	INSERT INTO dbo.locations
	VALUES(@new_location_id,'N0' + CAST(@new_location_id AS NVARCHAR(100)), @new_location,'store',NULL,GETDATE(),GETDATE())


	----------------------------------------------------------------------
	-- refreshing and returning the results
	SELECT '{"result": "OK", "caption": "Location Added", "data": ["refreshedRow"]}' AS result
	SELECT @new_location_id AS location_id, @new_location AS location

    END TRY
    BEGIN CATCH
		    DECLARE @sp_name NVARCHAR(500)
		    SELECT @sp_name = s.[name]+'.'+OBJECT_NAME(@@PROCID), @msg = 'Action could not be executed:'+ERROR_MESSAGE()
		    FROM sys.procedures p INNER JOIN sys.schemas s ON p.schema_id = s.schema_id
		    WHERE OBJECT_ID = @@PROCID
            EXEC core.event_insert @task_name = @@PROCID, @event_type_id = 106, @event_text = @msg
		    SELECT dbo.json_error('Action could not be executed') AS result
    END CATCH;
END
GO