<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Morgan Patou, auteur/autrice sur dbi Blog</title>
	<atom:link href="https://www.dbi-services.com/blog/author/morgan-patou/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.dbi-services.com/blog/author/morgan-patou/</link>
	<description></description>
	<lastBuildDate>Mon, 15 Jun 2026 08:20:50 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2025/05/cropped-favicon_512x512px-min-32x32.png</url>
	<title>Morgan Patou, auteur/autrice sur dbi Blog</title>
	<link>https://www.dbi-services.com/blog/author/morgan-patou/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>M-Files BD &#8211; Trend widgets: line and area</title>
		<link>https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/</link>
					<comments>https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Sat, 13 Jun 2026 16:34:04 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[area]]></category>
		<category><![CDATA[Business Dashboard]]></category>
		<category><![CDATA[line]]></category>
		<category><![CDATA[M-Files]]></category>
		<category><![CDATA[widget]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=45076</guid>

					<description><![CDATA[<p>This is the second widget post of the series, focused on the two trend widgets: line and area. The previous one covered scalar widgets (Post 4a). Since these two widgets are closely related, it made sense to bundle them together in the same post. Both surface a single value per category or per time bucket, [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/">M-Files BD &#8211; Trend widgets: line and area</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This is the second widget post of the series, focused on the two trend widgets: <strong><em>line</em></strong> and <strong><em>area</em></strong>. The previous one covered scalar widgets (<a href="https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/" id="45018" target="_blank" rel="noreferrer noopener">Post 4a</a>). Since these two widgets are closely related, it made sense to bundle them together in the same post. Both surface a single value per category or per time bucket, plotted across a horizontal axis. They are best used with the <strong><em>groupByDateBucket</em></strong> aggregation to show evolution over time, but they also accept <strong><em>groupByProperty</em></strong>.</p>



<h2 id="h-1-why-these-two-are-in-the-same-post" class="wp-block-heading">1. Why these two are in the same post</h2>



<p class="wp-block-paragraph">The wire format and the rendering pipeline for <strong><em>line</em></strong> and <strong><em>area</em></strong> are nearly identical. They produce the same JSON on the server, they use the same client code path, they accept the same aggregation types, the same reducers, the same <strong><em>display</em></strong> options. The only meaningful difference is visual: <strong><em>area</em></strong> fills the region under the line with a translucent color, while <strong><em>line</em></strong> leaves it transparent.</p>



<p class="wp-block-paragraph">In other words: choose <strong><em>area</em></strong> when you want to emphasize cumulative volume or magnitude, choose <strong><em>line</em></strong> when you want to emphasize the trajectory. Both communicate the same thing, it&#8217;s more about what you want to focus on.</p>



<h2 id="h-2-a-first-example-monthly-revenue-trend" class="wp-block-heading">2. A first example: monthly revenue trend</h2>



<p class="wp-block-paragraph">Here is the canonical example: total invoice amount per month for the ongoing year, displayed as a line chart.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;id&quot;: &quot;&quot;,
  &quot;title&quot;: &quot;Monthly revenue&quot;,
  &quot;type&quot;: &quot;line&quot;,
  &quot;gridColumnSpan&quot;: 12,
  &quot;gridRowSpan&quot;: 2,
  &quot;display&quot;: { &quot;smooth&quot;: &quot;Yes&quot;, &quot;decimals&quot;: 0 },
  &quot;query&quot;: {
    &quot;objectType&quot;: &quot;Document&quot;,
    &quot;class&quot;: &quot;Invoice&quot;,
    &quot;filters&quot;: &#x5B;
      { &quot;property&quot;: &quot;Invoice date&quot;, &quot;operator&quot;: &quot;between&quot;,
        &quot;value&quot;: &#x5B;&quot;@startOfYear&quot;, &quot;@endOfYear&quot;], &quot;valueType&quot;: &quot;dateToken&quot; }
    ],
    &quot;aggregation&quot;: {
      &quot;type&quot;: &quot;groupByDateBucket&quot;,
      &quot;propertyName&quot;: &quot;Invoice date&quot;,
      &quot;bucketSize&quot;: &quot;month&quot;,
      &quot;reducer&quot;: &quot;sum&quot;,
      &quot;reducerProperty&quot;: &quot;Amount&quot;,
      &quot;includeEmptyResults&quot;: &quot;Yes&quot;
    }
  }
}
</pre></div>


<p class="wp-block-paragraph">A few things to note in this JSON:</p>



<ul class="wp-block-list">
<li>The filter restricts the data to the current calendar year using two date tokens. The full date-token vocabulary is covered in the future Post 5.</li>



<li>The aggregation is <strong><em>groupByDateBucket</em></strong> on the same date property, bucketed by <strong><em>month</em></strong>. Each bucket is reduced with <strong><em>sum</em></strong> of the <strong><em>Amount</em></strong> property. In other words, this means that all invoices with a date within this calendar year will be grouped by month and the value coming from the Amount property will all be summed within each month to arrive to a Total per month.</li>



<li><strong><em>includeEmptyResults: &#8220;Yes&#8221;</em></strong> ensures that months with no invoices appear as zero values, so the trend line is continuous from January to December rather than skipping empty months (e.g. you were closed for vacations or weren&#8217;t able to sell anything for some other reasons).</li>
</ul>



<p class="wp-block-paragraph">Switching the widget <strong><em>type</em></strong> from <strong><em>line</em></strong> to <strong><em>area</em></strong> in this exact JSON gives the area-chart variant, nothing else changes:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c3194fc3&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c3194fc3" class="wp-block-image size-full is-style-default wp-lightbox-container"><img fetchpriority="high" decoding="async" width="1658" height="936" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1.png" alt="Line and area widgets" class="wp-image-45084" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1.png 1658w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1-300x169.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1-1024x578.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1-768x434.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.2-1-1536x867.png 1536w" sizes="(max-width: 1658px) 100vw, 1658px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h2 id="h-3-the-aggregation-types-these-widgets-accept" class="wp-block-heading">3. The aggregation types these widgets accept</h2>



<p class="wp-block-paragraph"><strong><em>line</em></strong> and <strong><em>area</em></strong> are valid with two aggregation types:</p>



<ul class="wp-block-list">
<li><strong><em>groupByDateBucket</em></strong>: the natural fit for time series. It groups a date / timestamp property into a certain bucket: <strong><em>day</em></strong>, <strong><em>week</em></strong>, <strong><em>month</em></strong>, <strong><em>quarter</em></strong>, or <strong><em>year</em></strong>.</li>



<li><strong><em>groupByProperty</em></strong>: groups by the distinct values of any property. It supports date/time properties, but it&#8217;s uncommon, as each day would be a data-point. It also support any other type of properties, to display them as categorical trends (e.g. &#8220;Monthly revenue per region&#8221; rendered as a line for some reason). Depending on requirements, a <strong><em>bar</em></strong> display might be more adapted.</li>
</ul>



<p class="wp-block-paragraph">The visual designer will only allow you to select one of these two aggregations. But if you try to set a <strong><em>summary</em></strong> or <strong><em>list</em></strong> aggregation in the JSON editor directly, then an error will be thrown, as it isn&#8217;t possible.</p>



<h2 id="h-4-display-smooth" class="wp-block-heading">4. display.smooth</h2>



<p class="wp-block-paragraph">The single most common <strong><em>display</em></strong> option for these widgets is <strong><em>smooth</em></strong>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;display&quot;: { &quot;smooth&quot;: &quot;Yes&quot; }
</pre></div>


<p class="wp-block-paragraph">When set to <strong><em>&#8220;Yes&#8221;</em></strong>, the line/area charts draws a Bézier-smoothed curve between data points (c.f. example above). When <strong><em>&#8220;No&#8221;</em></strong> (or omitted), the connecting line is made of straight segments. Smoothing makes monthly or quarterly trends look more visually pleasant. It can also slightly hide outliers, so use it where the trend story is more important than the exact per-bucket value.</p>



<h2 id="h-5-display-decimals" class="wp-block-heading">5. display.decimals</h2>



<p class="wp-block-paragraph">For numeric reducers (<strong><em>sum</em></strong>, <strong><em>avg</em></strong>, <strong><em>min</em></strong>, <strong><em>max</em></strong>, <strong><em>median</em></strong>), this controls how many decimal places are shown in tooltips (default <strong><em>0</em></strong>).</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;display&quot;: { &quot;smooth&quot;: &quot;Yes&quot;, &quot;decimals&quot;: 2 }
</pre></div>


<p class="wp-block-paragraph">In the monthly-revenue example above, if amounts are stored as floats (which is probably the case), you may want <strong><em>decimals: 2</em></strong> so the tooltip would show <strong><em>116&#8217;520.43</em></strong> rather than <strong><em>116&#8217;520</em></strong>.</p>



<h2 id="h-6-display-thresholds-in-single-series-mode" class="wp-block-heading">6. display.thresholds in single-series mode</h2>



<p class="wp-block-paragraph">In single-series mode (no <strong><em>seriesProperty</em></strong>), the <strong><em>thresholds</em></strong> array colors <strong>each data point marker</strong> based on its own value. The connecting line (and the area below) itself stays brand blue. The configuration is the same as on <strong><em>kpiNumber</em></strong> and <strong><em>gauge</em></strong>. You can either use text values representing colors or the HTML color code:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;display&quot;: {
  &quot;smooth&quot;: &quot;Yes&quot;,
  &quot;thresholds&quot;: &#x5B;
    { &quot;value&quot;: 0, &quot;color&quot;: &quot;red&quot; },
    { &quot;value&quot;: 40000, &quot;color&quot;: &quot;orange&quot; },
    { &quot;value&quot;: 100000, &quot;color&quot;: &quot;yellow&quot; },
    { &quot;value&quot;: 120000, &quot;color&quot;: &quot;green&quot; }
  ]
}
</pre></div>


<p class="wp-block-paragraph">This is useful to highlight specific buckets that crossed a business threshold without changing the overall trend visualization. As mentioned in <a href="https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/">Post 4a</a>, the default falls back to brand blue for values below the lowest threshold.</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c3195d43&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c3195d43" class="wp-block-image size-full wp-lightbox-container"><img decoding="async" width="1658" height="936" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1.png" alt="Line and area widgets with thresholds" class="wp-image-45087" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1.png 1658w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1-300x169.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1-1024x578.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1-768x434.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.6-1-1536x867.png 1536w" sizes="(max-width: 1658px) 100vw, 1658px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h2 id="h-7-seriesproperty-multiple-lines-on-the-same-chart" class="wp-block-heading">7. seriesProperty: multiple lines on the same chart</h2>



<p class="wp-block-paragraph">This is where these widgets become genuinely powerful. When you set a <strong><em>seriesProperty</em></strong> on the aggregation, the engine produces <strong>one line per distinct value</strong> of that additional property, with an automatic legend and color palette.</p>



<p class="wp-block-paragraph">For instance, splitting the monthly revenue line into separate lines per country:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;aggregation&quot;: {
  &quot;type&quot;: &quot;groupByDateBucket&quot;,
  &quot;propertyName&quot;: &quot;Invoice date&quot;,
  &quot;bucketSize&quot;: &quot;month&quot;,
  &quot;reducer&quot;: &quot;sum&quot;,
  &quot;reducerProperty&quot;: &quot;Amount&quot;,
  &quot;seriesProperty&quot;: &quot;Country&quot;,
  &quot;includeEmptyResults&quot;: &quot;Yes&quot;
}
</pre></div>


<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c31964c0&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c31964c0" class="wp-block-image size-full wp-lightbox-container"><img decoding="async" width="1656" height="1392" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1.png" alt="Line and area widgets total as well as multi-series per country" class="wp-image-45085" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1.png 1656w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1-300x252.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1-1024x861.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1-768x646.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4b.7-1-1536x1291.png 1536w" sizes="(max-width: 1656px) 100vw, 1656px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p class="wp-block-paragraph">A few notes that matter in practice:</p>



<ul class="wp-block-list">
<li><strong><em>display.smooth</em></strong> applies to every series. There is no per-series smoothing.</li>



<li><strong><em>display.thresholds</em></strong> are <strong>silently ignored</strong> in multi-series mode. If they were applied per-point, they would override the series palette colors and break the relationship between line color and legend label. So the engine drops them in multi-series and keeps the legend honest.</li>



<li><strong><em>includeEmptyResults</em></strong> in multi-series mode fills gaps across <strong>both dimensions</strong> (every series gets a zero in any time bucket where it has no data). This avoids broken lines.</li>
</ul>



<h3 id="h-7-1-use-low-cardinality-series-only" class="wp-block-heading">7.1. Use low-cardinality series only</h3>



<p class="wp-block-paragraph">The <strong><em>seriesProperty</em></strong> is best used with <strong>low-cardinality</strong> properties: lookup values, booleans, short text ranges. A monthly revenue chart with 5-6 customers is readable, but the same chart with two hundred customers becomes a colored mess where no individual line is distinguishable from the others. If there are too many series, it loses its usefulness. The validation wouldn&#8217;t block it though, since it is technically a valid compatibility. Try it, and see what it looks like!</p>



<h2 id="h-8-includeemptyresults-gap-filling" class="wp-block-heading">8. includeEmptyResults: gap filling</h2>



<p class="wp-block-paragraph">I mentioned this earlier already, and as you could see above, there are actually months with &#8220;0&#8221; values/amounts.</p>



<p class="wp-block-paragraph">With <strong><em>includeEmptyResults: &#8220;Yes&#8221;</em></strong> present, the graph will fill the gaps between the earliest and the latest data points found with zero values. In above example, we can see in March that there was 0 revenue in total. And similarly, in October, there was 0 revenue for the USA. There is no discontinuity in the lines, all months are present, even if there is &#8220;0&#8221; as a result.</p>



<p class="wp-block-paragraph">With <strong><em>includeEmptyResults: &#8220;No&#8221;</em></strong>, the chart will only display the buckets that have at least one matching object (across the series). So, if no invoice was issued in March, the X axis jumps from February to April directly. For above case, it would still display October, since there is a revenue in Switzerland, therefore the USA line will display a &#8220;0&#8221; value still, the line cannot be &#8220;cut&#8221;. Visually, that compresses the time scale and hides the meaningful &#8220;we had no revenue in March&#8221; signal.</p>



<p class="wp-block-paragraph">There are valid use cases for both situations, so just select the one that makes sense depending on what you want to see.</p>



<p class="wp-block-paragraph"><strong><em>Note:</em></strong> Adding <strong><em>includeEmptyResults: &#8220;Yes&#8221;</em></strong> can also add a <strong><em>(none)</em></strong> bucket for objects whose property used by the <strong><em>groupByProperty</em></strong> has no value. For example, if you group by a boolean state Yes/No, and some objects do not have a value on that boolean, they would end up in this <strong><em>(none)</em></strong> group. For <strong><em>groupByDateBucket</em></strong>, they usually don&#8217;t appear since you usually filter on a date in a certain range (like a month, or a year). Therefore if there is no date, usually it is just outside the range, but it technically also behaves in the same way if you fetch all results.</p>



<h2 id="h-9-the-no-date-valued-reducer-rule" class="wp-block-heading">9. The &#8220;no date-valued reducer&#8221; rule</h2>



<p class="wp-block-paragraph">Same rule as on chart widgets in general: <strong><em>line</em></strong> and <strong><em>area</em></strong> cannot render a reducer that returns a date string. If you write something like:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;aggregation&quot;: {
  &quot;type&quot;: &quot;groupByProperty&quot;,
  &quot;propertyName&quot;: &quot;Customer&quot;,
  &quot;reducer&quot;: &quot;max&quot;,
  &quot;reducerProperty&quot;: &quot;Invoice date&quot;
}
</pre></div>


<p class="wp-block-paragraph">Then, with a <strong><em>line</em></strong> widget, the display would contain a placeholder and the query validation would show a warning. The reason was discussed in the previous post: ISO dates cannot be plotted on a numeric axis at the moment, so it wouldn&#8217;t make sense as a display.</p>



<p class="wp-block-paragraph">The workaround is the same: use <strong><em>kpiNumber</em></strong> or a <strong><em>gauge</em></strong> in date mode if you only want to display one value, or a <strong><em>table</em></strong> for one or more values.</p>



<h2 id="h-10-drill-through-on-trend-widgets" class="wp-block-heading">10. Drill-through on trend widgets</h2>



<p class="wp-block-paragraph">When <strong><em>drillThroughEnabled</em></strong> is <strong><em>&#8220;Yes&#8221;</em></strong>, clicking a data point opens the drill-through modal with the objects in that bucket. In multi-series mode, the click respects the series the point belongs to: only objects matching both the time bucket <strong>and</strong> the series value are shown. This makes it easy to answer questions like &#8220;show me the May invoices from Switzerland&#8221; with a single click on the right point of the line.</p>



<h2 id="h-11-wrap-up" class="wp-block-heading">11. Wrap-up</h2>



<p class="wp-block-paragraph"><strong><em>line</em></strong> and <strong><em>area</em></strong> are the natural choice for time-series questions. They are fundamentally one widget with two visual modes, sharing the same aggregation contract and the same <strong><em>display</em></strong> vocabulary. The interesting part is what you can do with <strong><em>seriesProperty</em></strong>: turning a single trend into a multi-actor comparison without writing any extra logic.</p>



<p class="wp-block-paragraph">Post 4c picks up the remaining three widgets: <strong><em>donut</em></strong>, <strong><em>bar</em></strong>, and <strong><em>table</em></strong>. The grouping rationale will be that they all answer <strong>distribution</strong> questions (how a population breaks down by category) or <strong>tabular</strong> questions (give me the raw rows), as opposed to the time-trend questions that <strong><em>line</em></strong> and <strong><em>area</em></strong> handle.</p>



<p class="wp-block-paragraph">Want to know more about this Business Dashboard? <a href="https://www.dbi-services.com/company/contact/" target="_blank" rel="noreferrer noopener">Contact us</a> and we will be happy to showcase it on <a href="https://www.m-files.com/" target="_blank" rel="noreferrer noopener">M-Files</a>.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/">M-Files BD &#8211; Trend widgets: line and area</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>M-Files BD &#8211; Scalar widgets: kpiNumber and gauge</title>
		<link>https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/</link>
					<comments>https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Tue, 09 Jun 2026 18:09:44 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[Business Dashboard]]></category>
		<category><![CDATA[Gauge]]></category>
		<category><![CDATA[KPI]]></category>
		<category><![CDATA[kpiNumber]]></category>
		<category><![CDATA[M-Files]]></category>
		<category><![CDATA[widget]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=45018</guid>

					<description><![CDATA[<p>This is the first widget-specific post of the series. As a reminder, the previous post mapped the anatomy of a dashboard definition. From here on, I go widget by widget. I have grouped the seven widget types into three families: The reason kpiNumber and gauge sit together is that they accept exactly the same aggregation [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/">M-Files BD &#8211; Scalar widgets: kpiNumber and gauge</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This is the first widget-specific post of the series. As a reminder, the previous post mapped the <a href="https://www.dbi-services.com/blog/m-files-bd-anatomy-of-a-dashboard-definition/" target="_blank" rel="noreferrer noopener">anatomy of a dashboard definition</a>. From here on, I go widget by widget. I have grouped the seven widget types into three families:</p>



<ul class="wp-block-list">
<li><strong>Scalar widgets</strong>: <strong><em>kpiNumber</em></strong> and <strong><em>gauge</em></strong> both display a single value (a count, a sum, a date) over the matching object set. This is what the current post covers.</li>



<li><strong>Trend widgets</strong>: <strong><em>line</em></strong> and <strong><em>area</em></strong>, which are very similar both in form and in configuration, will be the next post, 4b.</li>



<li><strong>Distribution and tabular widgets</strong>: <strong><em>donut</em></strong>, <strong><em>bar</em></strong>, <strong><em>table</em></strong>, which are all remaining supported widgets (as of now), c.f. Post 4c later.</li>
</ul>



<p class="wp-block-paragraph">The reason <strong><em>kpiNumber</em></strong> and <strong><em>gauge</em></strong> sit together is that they accept exactly the same aggregation type (<strong><em>summary</em></strong>) and they share the same reducer model. So once you understand one, the other is mostly a different visual.</p>



<h2 class="wp-block-heading" id="h-1-the-summary-aggregation-in-one-paragraph">1. The summary aggregation in one paragraph</h2>



<p class="wp-block-paragraph">A full post will be dedicated to aggregations (Post 6 in the series), but to make this one self-contained: the <strong><em>summary</em></strong> aggregation returns a single value over all the matching objects. By default, it counts all matching objects. Its initial name was actually &#8220;count&#8221;, but later I wanted to introduce mathematical capabilities (c.f. the next sentence) so I had to change the name. With a <strong><em>reducer</em></strong> of <strong><em>sum</em></strong>, <strong><em>avg</em></strong>, <strong><em>median</em></strong>, <strong><em>min</em></strong>, or <strong><em>max</em></strong> (instead of the default <strong><em>count</em></strong>), it reduces a property&#8217;s values across the matching set instead.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
// Total count of matching objects
&quot;aggregation&quot;: {
  &quot;type&quot;: &quot;summary&quot;
}

// Sum of an &quot;amount&quot; property for all matching objects
&quot;aggregation&quot;: {
  &quot;type&quot;: &quot;summary&quot;,
  &quot;reducer&quot;: &quot;sum&quot;,
  &quot;reducerProperty&quot;: &quot;Amount&quot;
}

// Latest contract expiry date among all matching objects
&quot;aggregation&quot;: {
  &quot;type&quot;: &quot;summary&quot;,
  &quot;reducer&quot;: &quot;max&quot;,
  &quot;reducerProperty&quot;: &quot;Effective through&quot;
}
</pre></div>


<p class="wp-block-paragraph">Both <strong><em>kpiNumber</em></strong> and <strong><em>gauge</em></strong> accept this aggregation and only this one. The validator&#8217;s compatibility matrix is strict on that point. If you ever try to put a <strong><em>groupByProperty</em></strong> (or something else) on a KPI tile, you will not be able to save your dashboard.</p>



<h2 class="wp-block-heading" id="h-2-kpinumber-the-big-number-tile">2. kpiNumber &#8211; the big number tile</h2>



<p class="wp-block-paragraph">The <strong><em>kpiNumber</em></strong> is the simplest widget in the catalog. It shows a large number, with an optional small unit label below.</p>



<p class="wp-block-paragraph">Minimal example:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;id&quot;: &quot;&quot;,
  &quot;title&quot;: &quot;All Contracts&quot;,
  &quot;type&quot;: &quot;kpiNumber&quot;,
  &quot;gridColumnSpan&quot;: 3,
  &quot;gridRowSpan&quot;: 1,
  &quot;query&quot;: {
    &quot;objectType&quot;: &quot;Document&quot;,
    &quot;class&quot;: &quot;Contract or Agreement&quot;,
    &quot;aggregation&quot;: { &quot;type&quot;: &quot;summary&quot; }
  },
  &quot;display&quot;: { &quot;unit&quot;: &quot;contracts&quot; }
}
</pre></div>


<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c31a3481&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c31a3481" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="1726" height="240" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.2.png" alt="M-Files Busisiness Dashboard kpiNumber" class="wp-image-45019" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.2.png 1726w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.2-300x42.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.2-1024x142.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.2-768x107.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.2-1536x214.png 1536w" sizes="auto, (max-width: 1726px) 100vw, 1726px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h3 class="wp-block-heading" id="h-2-1-display-unit">2.1. display.unit</h3>



<p class="wp-block-paragraph">A small text label shown below the number (c.f. above screenshot), indicating the natural unit of the value: <strong><em>&#8220;contracts&#8221;</em></strong>, <strong><em>&#8220;invoices&#8221;</em></strong>, <strong><em>&#8220;CHF&#8221;</em></strong>, <strong><em>&#8220;open items&#8221;</em></strong> etc&#8230; This is purely optional, so you can omit it if the title is already self-explanatory.</p>



<h3 class="wp-block-heading" id="h-2-2-display-decimals">2.2. display.decimals</h3>



<p class="wp-block-paragraph">Number of decimal to use for the widget (default <strong><em>0</em></strong>). This is useful when the reducer isn&#8217;t the default <strong><em>count</em></strong>, since a count of object is mandatorily an integer. For example:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;display&quot;: { &quot;unit&quot;: &quot;CHF&quot;, &quot;decimals&quot;: 2 }
</pre></div>


<h3 class="wp-block-heading" id="h-2-3-display-thresholds">2.3. display.thresholds</h3>



<p class="wp-block-paragraph">This one is where the KPI tile becomes really useful in management dashboards. The <strong><em>thresholds</em></strong> array colors the number based on the current value. You can either use texts representing colors or the HTML color code:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;display&quot;: {
  &quot;unit&quot;: &quot;Overdue Invoices&quot;,
  &quot;thresholds&quot;: &#x5B;
    { &quot;value&quot;: 0,  &quot;color&quot;: &quot;green&quot;   },
    { &quot;value&quot;: 1, &quot;color&quot;: &quot;orange&quot;  },
    { &quot;value&quot;: 10, &quot;color&quot;: &quot;red&quot; }
  ]
}
</pre></div>


<p class="wp-block-paragraph">The engine applies the color of the highest threshold where <strong><em>currentValue &gt;= threshold.value</em></strong>. So in the example above:</p>



<ul class="wp-block-list">
<li>0 overdue invoices: green</li>



<li>1 to 9: orange</li>



<li>10 and above: red</li>



<li>Below 0 (which would be unusual for a count, but possible for a sum on a money property): falls back to brand blue (<strong><em>#006eef</em></strong>) with the above example. But nothing prevents you to set a threshold on negative values.</li>
</ul>



<p class="wp-block-paragraph">Please note that thresholds apply only to numeric values. When the KPI shows a date (e.g. <strong><em>max</em></strong> on a date property), thresholds are ignored and the brand color stays. That&#8217;s the current behavior, and it might change if there is a need that calls for it.</p>



<h3 class="wp-block-heading" id="h-2-4-reducer-types-on-kpinumber">2.4. Reducer types on kpiNumber</h3>



<p class="wp-block-paragraph">All six reducers are supported, with the standard property-type rules:</p>



<ul class="wp-block-list">
<li><strong><em>count</em></strong> <em>(default)</em>: count the number of objects and you do not need to specify the <strong><em>reducerProperty</em></strong> in this case, since it&#8217;s taken by default.</li>



<li><strong><em>sum</em></strong>, <strong><em>avg</em></strong>, <strong><em>median</em></strong>: numeric properties only.</li>



<li><strong><em>min</em></strong>, <strong><em>max</em></strong>: numeric properties <strong>or</strong> date / timestamp properties.</li>
</ul>



<p class="wp-block-paragraph">A few real-world combinations:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
// Count
{ &quot;type&quot;: &quot;summary&quot; }

// Total revenue
{ &quot;type&quot;: &quot;summary&quot;, &quot;reducer&quot;: &quot;sum&quot;, &quot;reducerProperty&quot;: &quot;Amount&quot; }

// Average duration
{ &quot;type&quot;: &quot;summary&quot;, &quot;reducer&quot;: &quot;avg&quot;, &quot;reducerProperty&quot;: &quot;Project duration days&quot; }

// Latest contract expiry
{ &quot;type&quot;: &quot;summary&quot;, &quot;reducer&quot;: &quot;max&quot;, &quot;reducerProperty&quot;: &quot;Effective through&quot; }

// Oldest open invoice
{ &quot;type&quot;: &quot;summary&quot;, &quot;reducer&quot;: &quot;min&quot;, &quot;reducerProperty&quot;: &quot;Invoice date&quot; }
</pre></div>


<p class="wp-block-paragraph">The last two return ISO date strings internally, which is then displayed on the end-user side as <strong><em>DD/MM/YYYY</em></strong>, currently. So, in M-Files Sample Vault you can build a KPI like &#8220;Latest contract expiry: 31/12/2026&#8221; with three lines of JSON.</p>



<h3 class="wp-block-heading" id="h-2-5-drill-through-on-kpinumber">2.5. Drill-through on kpiNumber</h3>



<p class="wp-block-paragraph">When <strong><em>drillThroughEnabled</em></strong> is <strong><em>&#8220;Yes&#8221;</em></strong> at the dashboard level, the entire KPI tile is clickable. A click opens the drill-through modal with all the objects that contributed to the value. This is useful for all types of reducers, to get more details on how the dashboard reached that value.</p>



<h2 class="wp-block-heading" id="h-3-gauge-the-dial">3. gauge &#8211; the dial</h2>



<p class="wp-block-paragraph">The <strong><em>gauge</em></strong> is also driven by a <strong><em>summary</em></strong> aggregation, but it shows the value on a dial against a defined range. It comes in two flavors depending on the property selected for the reducer.</p>



<h3 class="wp-block-heading" id="h-3-1-numeric-mode">3.1. Numeric mode</h3>



<p class="wp-block-paragraph">This is the default mode and it can be used with any reducer: <strong><em>count</em></strong>, <strong><em>sum</em></strong>, <strong><em>avg</em></strong>, <strong><em>median</em></strong>, or <strong><em>min</em></strong> / <strong><em>max</em></strong> on a numeric property. The scale is in the natural units of the value, with <strong><em>display.min</em></strong> and <strong><em>display.max</em></strong> bracketing the dial.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;id&quot;: &quot;&quot;,
  &quot;title&quot;: &quot;Overdue Invoices&quot;,
  &quot;type&quot;: &quot;gauge&quot;,
  &quot;gridColumnSpan&quot;: 6,
  &quot;gridRowSpan&quot;: 2,
  &quot;display&quot;: { &quot;min&quot;: 0, &quot;max&quot;: 50, &quot;unit&quot;: &quot;invoices&quot; },
  &quot;query&quot;: {
    &quot;objectType&quot;: &quot;Document&quot;,
    &quot;class&quot;: &quot;Purchase Invoice&quot;,
    &quot;filters&quot;: &#x5B;
      { &quot;property&quot;: &quot;Deadline&quot;, &quot;operator&quot;: &quot;lessThan&quot;,
        &quot;value&quot;: &quot;@today&quot;, &quot;valueType&quot;: &quot;dateToken&quot; }
    ],
    &quot;aggregation&quot;: { &quot;type&quot;: &quot;summary&quot; }
  }
}
</pre></div>


<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c31a44aa&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c31a44aa" class="wp-block-image size-medium wp-lightbox-container"><img loading="lazy" decoding="async" width="300" height="210" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.3.1-1-300x210.png" alt="" class="wp-image-45024" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.3.1-1-300x210.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.3.1-1.png 676w" sizes="auto, (max-width: 300px) 100vw, 300px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h3 class="wp-block-heading" id="h-3-2-display-thresholds-on-a-gauge">3.2. display.thresholds on a gauge</h3>



<p class="wp-block-paragraph">Same shape as the <strong><em>kpiNumber</em></strong>, but the gauge applies the colors to both the <strong>pointer</strong> and the <strong>arc segments</strong>, so the dial visually splits into zones:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;display&quot;: {
  &quot;min&quot;: 0, &quot;max&quot;: 50, &quot;unit&quot;: &quot;invoices&quot;,
  &quot;thresholds&quot;: &#x5B;
    { &quot;value&quot;: 0, &quot;color&quot;: &quot;green&quot;  },
    { &quot;value&quot;: 5, &quot;color&quot;: &quot;orange&quot;  },
    { &quot;value&quot;: 20, &quot;color&quot;: &quot;red&quot; }
  ]
}
</pre></div>


<p class="wp-block-paragraph">This colors the arc as: 0 to 5 in green, 5 to 20 in orange, 20 and above in red. The pointer also takes the color of the zone where the current value lands. In other words, a glance at the dial tells you whether you are in a comfortable zone, a warning zone, or a critical one (c.f. above screenshot for the example with the colors).</p>



<h3 class="wp-block-heading" id="h-3-3-date-mode">3.3. Date mode</h3>



<p class="wp-block-paragraph">When the <strong><em>reducer</em></strong> is <strong><em>min</em></strong> or <strong><em>max</em></strong> and <strong><em>reducerProperty</em></strong> is a date / timestamp property, the gauge switches to <strong>date mode</strong> automatically. The dial then represents a day offset relative to today:</p>



<ul class="wp-block-list">
<li><strong><em>display.min</em></strong> and <strong><em>display.max</em></strong> are expressed in <strong>day units</strong>, where <strong><em>0</em></strong> is today, negative values are in the past, positive values are in the future.</li>



<li>Graduation labels show dates like <strong><em>DD MMM</em></strong> if all dates are within a 365 timeframe, or it adds the <strong><em>YY</em></strong> if it goes beyond, so you know which date exactly we are talking about.</li>



<li>The center detail shows the actual date <strong><em>DD/MM/YYYY</em></strong> that comes from the reducer.</li>
</ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;id&quot;: &quot;&quot;,
  &quot;title&quot;: &quot;Closest Expiry&quot;,
  &quot;type&quot;: &quot;gauge&quot;,
  &quot;gridColumnSpan&quot;: 6,
  &quot;gridRowSpan&quot;: 2,
  &quot;display&quot;: { &quot;min&quot;: 0, &quot;max&quot;: 180, &quot;unit&quot;: &quot;next expiry&quot; },
  &quot;query&quot;: {
    &quot;objectType&quot;: &quot;Document&quot;,
    &quot;class&quot;: &quot;Contract or Agreement&quot;,
    &quot;filters&quot;: &#x5B;
      {
        &quot;property&quot;: &quot;Effective through&quot;,
        &quot;operator&quot;: &quot;greaterOrEqual&quot;,
        &quot;value&quot;: &quot;@today&quot;,
        &quot;valueType&quot;: &quot;dateToken&quot;
      }
    ],
    &quot;aggregation&quot;: {
      &quot;type&quot;: &quot;summary&quot;,
      &quot;reducer&quot;: &quot;min&quot;,
      &quot;reducerProperty&quot;: &quot;Effective through&quot;
    }
  },
}
</pre></div>


<p class="wp-block-paragraph">In this example, the dial spans from today to 180 days in the future. If the earliest contract expiry is 22 days from today, the needle lands at the date that represents 22 and the center shows the actual date. If it is already in the past (negative offset), it indicates an overdue situation immediately. (in below screenshot, I&#8217;m using dates related to the past, as that&#8217;s what is available in the Sample Vault from M-Files, but in a real-world, the above JSON is usable)</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c31a4e75&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c31a4e75" class="wp-block-image size-medium wp-lightbox-container"><img loading="lazy" decoding="async" width="300" height="211" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.3.3-300x211.png" alt="M-Files Busisiness Dashboard gauge date-mode" class="wp-image-45021" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.3.3-300x211.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/4a.3.3.png 674w" sizes="auto, (max-width: 300px) 100vw, 300px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h3 class="wp-block-heading" id="h-3-4-when-the-reducer-cannot-produce-a-numeric-value">3.4. When the reducer cannot produce a numeric value</h3>



<p class="wp-block-paragraph">If you try to use a date reducer on a chart widget (<strong><em>bar</em></strong>, <strong><em>line</em></strong>, <strong><em>area</em></strong>, <strong><em>donut</em></strong>), the widget will show a grey placeholder text, explaining that it is not supported (doesn&#8217;t really make sense). Moreover, before that, the administrator that designed the dashboard also got the same warning that this isn&#8217;t something that would work.</p>



<p class="wp-block-paragraph">On a <strong><em>gauge</em></strong>, you have the date-mode escape hatch. On a <strong><em>kpiNumber</em></strong> or a <strong><em>table</em></strong>, the value is just formatted as a date string. So as a rule, <strong>date-valued</strong> reducers belong on <strong><em>kpiNumber</em></strong>, <strong><em>table</em></strong>, or <strong><em>gauge</em></strong> (date mode). They do not belong on chart widgets.</p>



<p class="wp-block-paragraph">As said, this rule is not arbitrary, it is what the engine actually enforces. The &#8220;Test Widget&#8221;, &#8220;Test Queries&#8221; or &#8220;Save&#8221; will all tell you the same thing in a friendly way, if you slip or forgot about it.</p>



<h2 class="wp-block-heading" id="h-4-summary-of-reducers-for-scalar-widgets">4. Summary of reducers for scalar widgets</h2>



<p class="wp-block-paragraph">To close this post, here is a quick reference of what works on <strong><em>kpiNumber</em></strong> and <strong><em>gauge</em></strong>:</p>



<figure class="wp-block-table"><table><thead><tr><th>Reducer</th><th>What it computes</th><th>reducerProperty types</th><th>Empty set returns</th></tr></thead><tbody><tr><td><strong><em>count</em></strong> <em>(default)</em></td><td>Number of matching objects</td><td>n/a</td><td><strong><em>0</em></strong></td></tr><tr><td><strong><em>sum</em></strong></td><td>Total of the property&#8217;s values</td><td>Numeric</td><td><strong><em>null</em></strong> (rendered as <strong><em>&#8211;</em></strong>)</td></tr><tr><td><strong><em>avg</em></strong></td><td>Arithmetic mean</td><td>Numeric</td><td><strong><em>null</em></strong> (rendered as <strong><em>&#8211;</em></strong>)</td></tr><tr><td><strong><em>median</em></strong></td><td>Middle value</td><td>Numeric</td><td><strong><em>null</em></strong> (rendered as <strong><em>&#8211;</em></strong>)</td></tr><tr><td><strong><em>min</em></strong></td><td>Smallest value</td><td>Numeric or date / timestamp</td><td><strong><em>null</em></strong> (rendered as <strong><em>&#8211;</em></strong>)</td></tr><tr><td><strong><em>max</em></strong></td><td>Largest value</td><td>Numeric or date / timestamp</td><td><strong><em>null</em></strong> (rendered as <strong><em>&#8211;</em></strong>)</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">As described above, there is a subtle difference of &#8220;0&#8221; vs &#8220;-&#8221; for the empty results. For the default <strong><em>count</em></strong>, we display &#8220;0&#8221;, as there was really no objects returned, so the count of them is correct. For all other reducers, if there is no results, then there is nothing to do maths on. To differentiate between &#8220;no results&#8221; and &#8220;sum is really 0&#8221; (e.g. -2+2), I decided to display &#8220;-&#8221; for cases where there is no actual data to compute.</p>



<p class="wp-block-paragraph">Of course, this empty results set difference is only true when there are no data returned. So if your filter is accurate and you expect some data, then it will never happen.</p>



<h2 class="wp-block-heading" id="h-5-closing-thoughts">5. Closing thoughts</h2>



<p class="wp-block-paragraph"><strong><em>kpiNumber</em></strong> and <strong><em>gauge</em></strong> are the two widgets that typically stands at the top of a dashboard. It can be in a row of four KPIs and a gauge below, or side-by-side, depending on the layout defined. They give the at-a-glance answer that motivated the whole module in the first place.</p>



<p class="wp-block-paragraph">In the next post (4b), I will talk about the trend widgets, <strong><em>line</em></strong> and <strong><em>area</em></strong>, which work in pairs almost as closely as <strong><em>kpiNumber</em></strong> and <strong><em>gauge</em></strong> do here.</p>



<p class="wp-block-paragraph">Want to know more about this Business Dashboard? <a href="https://www.dbi-services.com/company/contact/" target="_blank" rel="noreferrer noopener">Contact us</a> and we will be happy to showcase it on <a href="https://www.m-files.com/" target="_blank" rel="noreferrer noopener">M-Files</a>.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/">M-Files BD &#8211; Scalar widgets: kpiNumber and gauge</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>M-Files BD &#8211; Anatomy of a dashboard definition</title>
		<link>https://www.dbi-services.com/blog/m-files-bd-anatomy-of-a-dashboard-definition/</link>
					<comments>https://www.dbi-services.com/blog/m-files-bd-anatomy-of-a-dashboard-definition/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Sun, 07 Jun 2026 14:52:03 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[Business Dashboard]]></category>
		<category><![CDATA[Dashboard]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[M-Files]]></category>
		<category><![CDATA[widget]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=44952</guid>

					<description><![CDATA[<p>In the previous post, I walked through what an end user sees when they open the Business Dashboard pane in M-Files. From this post on, the audience shifts more towards the administrator&#8217;s side, looking at what a dashboard definition looks like. Every dashboard is a single JSON document. The Admin tab in M-Files Admin offers [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-anatomy-of-a-dashboard-definition/">M-Files BD &#8211; Anatomy of a dashboard definition</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In the <a href="https://www.dbi-services.com/blog/m-files-bd-end-user-experience/" id="44932" target="_blank" rel="noreferrer noopener">previous post</a>, I walked through what an end user sees when they open the Business Dashboard pane in M-Files. From this post on, the audience shifts more towards the administrator&#8217;s side, looking at what a dashboard definition looks like.</p>



<p class="wp-block-paragraph">Every dashboard is a single JSON document. The Admin tab in M-Files Admin offers a Visual Designer that builds this JSON for you, but in the end the JSON is the source of truth, so I think it is worth understanding its shape before getting into widget types and queries in the next posts. Think of this post as the <strong>reference map</strong> the rest of the series will refer back to.</p>



<h2 class="wp-block-heading" id="h-1-the-minimum-valid-dashboard-definition">1. The minimum valid dashboard definition</h2>



<p class="wp-block-paragraph">Before discussing every field, here is the (almost) smallest dashboard JSON that the engine accepts. It is intentionally trivial &#8211; a single KPI widget counting all Documents in the vault:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;schemaVersion&quot;: 1,
  &quot;id&quot;: &quot;&quot;,
  &quot;name&quot;: &quot;My Dashboard&quot;,
  &quot;widgets&quot;: &#x5B;
    {
      &quot;id&quot;: &quot;&quot;,
      &quot;title&quot;: &quot;Total Documents&quot;,
      &quot;type&quot;: &quot;kpiNumber&quot;,
      &quot;query&quot;: {
        &quot;objectType&quot;: &quot;Document&quot;,
        &quot;aggregation&quot;: { &quot;type&quot;: &quot;summary&quot; }
      }
    }
  ]
}
</pre></div>


<p class="wp-block-paragraph"><strong>Note 1:</strong> Boolean-like fields are exposed as the strings <strong><em>&#8220;Yes&#8221;</em></strong> or <strong><em>&#8220;No&#8221;</em></strong>. This is what M-Files shows as booleans, don&#8217;t ask me why it&#8217;s not True/False like the rest of the world ;).</p>



<p class="wp-block-paragraph"><strong>Note 2:</strong> Display names (object types, classes, properties, value-list items) must match what the vault shows in M-Files Admin. They are case-insensitive but must otherwise be exact.</p>



<h2 class="wp-block-heading" id="h-2-the-dashboard-level-fields">2. The dashboard-level fields</h2>



<p class="wp-block-paragraph">The top-level object can carry the following fields: required ones are marked, all others are optional with default values that make sense, usually.</p>



<h3 class="wp-block-heading" id="h-2-1-identification-and-presentation">2.1. Identification and presentation</h3>



<p class="wp-block-paragraph">These are the main parameters of a dashboard that you will usually set at the beginning:</p>



<ul class="wp-block-list">
<li><strong><em>schemaVersion</em></strong>: Integer (currently <strong><em>1</em></strong>). The <strong>Business Dashboard</strong> validation process emits a warning when this field is absent or lower than expected. This version is incremented when breaking changes are introduced, so admins will automatically be flagged that they need to update something.</li>



<li><strong><em>id</em></strong> <em>(required)</em>: typically a UUID v4 value (e.g. <strong><em>&#8220;12345678-abcd-1234-5678-abcd12345678&#8221;</em></strong>) but it isn&#8217;t restricted to that (e.g. <strong><em>&#8220;d01-contracts&#8221;</em></strong> works as well). The ID should be unique for each dashboard, otherwise you risk overriding an existing dashboard when you save. You can leave the field ID empty (as done above), the <strong>Business Dashboard</strong> will automatically generate one for you.</li>



<li><strong><em>name</em></strong> <em>(required)</em>: Text shown in the end-user dropdown, you should keep it concise (&lt;250 chars).</li>



<li><strong><em>description</em></strong>: Optional subtitle shown in italic below the top bar (hidden when absent). As described in the previous post, the first two lines will be shown on the user side, with a hover tooltip for the full text.</li>
</ul>



<h3 class="wp-block-heading" id="h-2-2-auto-refresh">2.2. Auto-refresh</h3>



<p class="wp-block-paragraph">These are parameters that controls whether or not the dashboard will refresh itself or allows controls about the refresh more globally:</p>



<ul class="wp-block-list">
<li><strong><em>autoRefreshEnabled</em></strong>: <strong><em>&#8220;Yes&#8221;</em></strong> or <strong><em>&#8220;No&#8221;</em></strong> (default <strong><em>&#8220;No&#8221;</em></strong>). Whether auto-refresh is enabled when the dashboard first loads.</li>



<li><strong><em>autoRefreshIntervalSeconds</em></strong>: Integer (default <strong><em>300</em></strong>, minimum <strong><em>15</em></strong>). Typical values can be <strong><em>60</em></strong>, <strong><em>300</em></strong> or <strong><em>900</em></strong> for example. This is the interval of time between two auto-refresh of all widgets done when the auto-refresh is enabled (either via <strong><em>autoRefreshEnabled</em></strong> or because <strong><em>userCanToggleAutoRefresh</em></strong> is <strong><em>&#8220;Yes&#8221;</em></strong> and a user enabled it manually). The minimum is set to 15s to avoid overloading the vault in case of mis-configuration.</li>



<li><strong><em>userCanToggleAutoRefresh</em></strong>: <strong><em>&#8220;Yes&#8221;</em></strong> or <strong><em>&#8220;No&#8221;</em></strong> (default <strong><em>&#8220;No&#8221;</em></strong>). Whether the user can disable or enable the auto-refresh feature.</li>
</ul>



<h3 class="wp-block-heading" id="h-2-3-exports-and-drill-through">2.3. Exports and drill-through</h3>



<p class="wp-block-paragraph">These are the optional features that you can enable:</p>



<ul class="wp-block-list">
<li><strong><em>exportToPdfEnabled</em></strong>: <strong><em>&#8220;Yes&#8221;</em></strong> or <strong><em>&#8220;No&#8221;</em></strong> (default <strong><em>&#8220;No&#8221;</em></strong>). Shows the ⎙ PDF button in the top bar, to allow users to export the dashboard to PDF.</li>



<li><strong><em>exportToCsvEnabled</em></strong>: <strong><em>&#8220;Yes&#8221;</em></strong> or <strong><em>&#8220;No&#8221;</em></strong> (default <strong><em>&#8220;No&#8221;</em></strong>). Shows the ⊞ CSV button on all widgets (except kpiNumber &amp; gauge) as well as on the drill-through modal, to allow users to export the widget details to CSV.</li>



<li><strong><em>drillThroughEnabled</em></strong>: <strong><em>&#8220;Yes&#8221;</em></strong> or <strong><em>&#8220;No&#8221;</em></strong> (default <strong><em>&#8220;No&#8221;</em></strong>). Makes chart elements and rows clickable to open the drill-through modal (c.f. previous post if you don&#8217;t know what the drill-through is).</li>
</ul>



<p class="wp-block-paragraph">These three flags are intentionally off by default. Turning them on is an explicit choice per dashboard, which I think is the right place to make that decision. A dashboard meant for casual &#8220;at-a-glance&#8221; use might not need them while a dashboard meant for active investigation might.</p>



<h3 class="wp-block-heading" id="h-2-4-performance-settings">2.4. Performance settings</h3>



<p class="wp-block-paragraph">These are security and performance settings:</p>



<ul class="wp-block-list">
<li><strong><em>skipTemplateCheck</em></strong>: <strong><em>&#8220;Yes&#8221;</em></strong> or <strong><em>&#8220;No&#8221;</em></strong> (default <strong><em>&#8220;No&#8221;</em></strong>). When <strong><em>&#8220;No&#8221;</em></strong> (default), objects marked as template (i.e. they have the &#8220;is Template&#8221; property and it is checked) are automatically excluded from any results. When <strong><em>&#8220;Yes&#8221;</em></strong>, this additional verification is skipped and templates can therefore be part of the results (faster execution).</li>



<li><strong><em>skipObjectPermissionCheck</em></strong>: <strong><em>&#8220;Yes&#8221;</em></strong> or <strong><em>&#8220;No&#8221;</em></strong> (default <strong><em>&#8220;No&#8221;</em></strong>). When <strong><em>&#8220;No&#8221;</em></strong> (default), every queries will respect the user-level accesses / ACLs, so different users might see different results / numbers / lists of objects, depending on their permissions. When <strong><em>&#8220;Yes&#8221;</em></strong>, server-level accesses are used, so different users will see the same results, always (faster execution).</li>



<li><strong><em>serverScanMaxResults</em></strong>: Integer (default <strong><em>500</em></strong>). The maximum number of M-Files objects fetched per widget query. You can set it to <strong><em>0</em></strong> for unlimited.</li>



<li><strong><em>drillThroughMaxResults</em></strong>: Integer (default <strong><em>300</em></strong>). Maximum rows shown in drill-through modals and <strong><em>list</em></strong> tables. You can set it to <strong><em>0</em></strong> for unlimited. The pagination for the opened modal is set at 15 rows per page. Therefore, there can be up to 20 pages by default.</li>
</ul>



<p class="wp-block-paragraph">I will dedicate a full post (Post 8 in the series) to these details and to the trade-offs they imply, so I will not spend too much time on them here. The defaults are reasonable for most use-cases but you can change everything, of course.</p>



<h3 class="wp-block-heading" id="h-2-5-access-control">2.5. Access control</h3>



<p class="wp-block-paragraph">Finally, the last parameter is about access control to the dashboard:</p>



<ul class="wp-block-list">
<li><strong><em>assignedTo</em></strong>: An object with two optional arrays, <strong><em>users</em></strong> and <strong><em>groups</em></strong>. When the arrays are absent or empty, the dashboard is public. This means that ANY M-Files user will be able to select the dashboard from the dropdown. It does NOT mean that he will see something inside the widgets! Therefore, it gives access to the dashboard itself, but widget content still relies on the user&#8217;s ACLs by default (or server-level access if <strong><em>skipObjectPermissionCheck</em></strong> is set to <strong><em>&#8220;Yes&#8221;</em></strong>, c.f. above section). On the other hand, when at least one of the arrays is present, access is granted to any user who appears in the <strong><em>users</em></strong> array <strong>OR</strong> is a member of any group in the <strong><em>groups</em></strong> array.</li>
</ul>



<p class="wp-block-paragraph">In this example, the dashboard will be accessible by the user whose Login Account is <strong><em>Morgan.Patou</em></strong> OR any users part of the <strong><em>Finance Team</em></strong> OR <strong><em>Management</em></strong> groups:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;assignedTo&quot;: {
  &quot;users&quot;: &#x5B;&quot;Morgan.Patou&quot;],
  &quot;groups&quot;: &#x5B;&quot;Finance Team&quot;, &quot;Management&quot;]
}
</pre></div>


<h3 class="wp-block-heading" id="h-2-6-widgets">2.6. Widgets</h3>



<ul class="wp-block-list">
<li><strong><em>widgets</em></strong> <em>(required)</em>: An array of widget objects. The order in this array is the order they appear on the grid. The size of the widget (column span, row span) is part of each widget&#8217;s definition (i.e. inside the array).</li>
</ul>



<h2 class="wp-block-heading" id="h-3-the-widget-object">3. The widget object</h2>



<p class="wp-block-paragraph">Each entry in the <strong><em>widgets</em></strong> array has the following shape:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;id&quot;: &quot;&quot;,
  &quot;title&quot;: &quot;My Widget&quot;,
  &quot;description&quot;: &quot;An example description for My Widget&quot;,
  &quot;type&quot;: &quot;kpiNumber&quot;,
  &quot;gridColumnSpan&quot;: 3,
  &quot;gridRowSpan&quot;: 1,
  &quot;query&quot;: { ... },
  &quot;display&quot;: { ... }
}
</pre></div>


<p class="wp-block-paragraph">Here is a quick description of the different fields:</p>



<ul class="wp-block-list">
<li><strong><em>id</em></strong> <em>(required)</em>:  Same description as for the dashboard</li>



<li><strong><em>title</em></strong> <em>(required)</em>:  Name of the widget shown at the top of the tile</li>



<li><strong><em>description</em></strong>: Optional one-line italic subtitle just below the title. Annotate what the widget measures, or flag a caveat</li>



<li><strong><em>type</em></strong> <em>(required)</em>:  One of <strong><em>kpiNumber</em></strong>, <strong><em>gauge</em></strong>, <strong><em>donut</em></strong>, <strong><em>bar</em></strong>, <strong><em>line</em></strong>, <strong><em>area</em></strong>, <strong><em>table</em></strong> (covered in posts 4a, 4b, 4c)</li>



<li><strong><em>gridColumnSpan</em></strong>: Integer from 1 to 12 (default <strong><em>4</em></strong>). Represent the width of the widget on the dashboard</li>



<li><strong><em>gridRowSpan</em></strong>: Integer (default <strong><em>1</em></strong>). Represent the height of the widget on the dashboard</li>



<li><strong><em>query</em></strong> <em>(required)</em>:  The query the widget will execute. This can be pretty complex, so it will be detailed in Post 5 (filters) and Post 6 (aggregations), later</li>



<li><strong><em>display</em></strong>: Type-specific display options, like color thresholds, unit, decimals, etc. It will be covered per widget type in posts 4a, 4b, 4c</li>
</ul>



<h2 class="wp-block-heading" id="h-4-the-query-object-in-one-paragraph">4. The query object in one paragraph</h2>



<p class="wp-block-paragraph">As mentioned, the detailed walkthrough of queries is the topic of Post 5 / 6, but for completeness, here is the basic shape of a query:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&quot;query&quot;: {
  &quot;objectType&quot;: &quot;Document&quot;,
  &quot;class&quot;: &quot;Contract or Agreement&quot;,
  &quot;filters&quot;: &#x5B;
    {
      &quot;property&quot;: &quot;Effective through&quot;,
      &quot;operator&quot;: &quot;greaterOrEqual&quot;,
      &quot;value&quot;: &quot;@today&quot;,
      &quot;valueType&quot;: &quot;dateToken&quot;
    }
  ],
  &quot;aggregation&quot;: {
    &quot;type&quot;: &quot;groupByProperty&quot;,
    &quot;propertyName&quot;: &quot;Agreement type&quot;,
    &quot;resolveValueListLabels&quot;: &quot;Yes&quot;,
    &quot;includeEmptyResults&quot;: &quot;No&quot;
  }
}
</pre></div>


<p class="wp-block-paragraph"><strong><em>objectType</em></strong> can be a string or an array of strings (multi-object-type query). <strong><em>class</em></strong> is optional but strongly recommended for performance and to check only objects that actually matter&#8230; For example, you don&#8217;t need to fetch all documents (irrespective of their class) if you only want to count the number of active contracts. <strong><em>filters</em></strong> is an array of 1-to-N filter combined by <strong>AND</strong>. Finally, <strong><em>aggregation</em></strong> specifies the shape of the result and the reduction applied. To know more about these, please wait for the next posts.</p>



<h2 class="wp-block-heading" id="h-5-the-12-column-grid">5. The 12-column grid</h2>



<p class="wp-block-paragraph">The layout is computed on a CSS grid with 12 columns. <strong><em>gridColumnSpan</em></strong> sets the width of a widget, while <strong><em>gridRowSpan</em></strong> sets its height in row units.</p>



<p class="wp-block-paragraph">Therefore, most common column layouts include:</p>



<ul class="wp-block-list">
<li>4 widgets per row: <strong><em>gridColumnSpan: 3</em></strong> each. Typical for KPI tiles.</li>



<li>3 widgets per row: <strong><em>gridColumnSpan: 4</em></strong> each. Medium tiles or narrow charts.</li>



<li>2 widgets per row: <strong><em>gridColumnSpan: 6</em></strong> each. Charts and tables.</li>



<li>1 widget per row: <strong><em>gridColumnSpan: 12</em></strong>. Full-width bar chart or wide table.</li>
</ul>



<p class="wp-block-paragraph">These are just examples. Nothing prevents you to set 3 widgets on a row with <strong><em>gridColumnSpan: 5</em></strong>, <strong><em>gridColumnSpan: 4</em></strong>, <strong><em>gridColumnSpan: 3</em></strong> respectively. The Visual Designer is great for that since you get an exact view of what it will look like directly while creating the dashboard. More on that on the Post 9a.</p>



<p class="wp-block-paragraph">The widgets simply fill the rows from left to right in the order they appear in the <strong><em>widgets</em></strong> array, wrapping to the next row when a row&#8217;s spans sum exceeds 12. So planning the layout is largely a matter of ordering the widgets and giving each one a sensible column span.</p>



<p class="wp-block-paragraph">For row height, a small subtlety: <strong><em>gridRowSpan: 2</em></strong> does not give you exactly twice the canvas height of <strong><em>gridRowSpan: 1</em></strong>. It actually gives you roughly <strong>three times</strong> the canvas height, because each row unit subtracts a fixed overhead (header, padding, borders) from the available space. The practical consequence is that <strong>chart widgets</strong> (<strong><em>bar</em></strong>, <strong><em>line</em></strong>, <strong><em>area</em></strong>, <strong><em>donut</em></strong>, <strong><em>gauge</em></strong>) should almost always use <strong><em>gridRowSpan: 2</em></strong> or higher. A single-row chart has very little drawing space and ends up cramped.</p>



<p class="wp-block-paragraph">KPI number tiles, on the other hand, look fine at <strong><em>gridRowSpan: 1</em></strong> and that is the right default for them. As a rule of thumb: KPIs at 1, everything else at 2.</p>



<h2 class="wp-block-heading" id="h-6-where-the-definition-is-stored">6. Where the definition is stored</h2>



<p class="wp-block-paragraph">Dashboard definitions are stored in the vault&#8217;s <strong>Named Value Storage</strong> (NVS). They are NOT stored as a specific object in the vault. I initially thought about creating them as standard vault objects but in the end decided the NVS would probably make it easier for deployments and usage in the long run. The trade-offs of that choice:</p>



<ul class="wp-block-list">
<li><strong>Pros</strong>: no extra object type to provision when installing the module, no ACL setup, no admin training on a new object type, no per-customer deployment friction. Backups of the vault include the dashboards automatically because NVS is part of the vault.</li>



<li><strong>Cons</strong>: there is no per-definition version history of the kind you would get with vault objects. I mitigated this by offering Import and Export buttons in the Admin tab that produce <strong><em>.json</em></strong> files. What I would recommend is to manage those files in a Git repository, which gives a clean and conventional version history outside the vault, with diff support.</li>
</ul>



<p class="wp-block-paragraph">The Export / Import buttons are the topic of Post 7. The TL;DR is that I would recommend that you should develop your dashboards on a DEV. Then, export them all into your git. Finally, when ready, import them into the TEST/QA/PROD environments, either manually or via CI/CD pipelines.</p>



<h2 class="wp-block-heading" id="h-7-quick-overview-of-a-full-dashboard-json-definition">7. Quick overview of a full dashboard JSON definition</h2>



<p class="wp-block-paragraph">Putting everything together, here is a slightly more complete (but still small) example. It has some of the dashboard-level fields modified from their default values to show-case actions and buttons, and two widgets:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
{
  &quot;schemaVersion&quot;: 1,
  &quot;id&quot;: &quot;&quot;,
  &quot;name&quot;: &quot;Contracts&quot;,
  &quot;description&quot;: &quot;Quick overview of contracts&quot;,
  &quot;autoRefreshEnabled&quot;: &quot;Yes&quot;,
  &quot;autoRefreshIntervalSeconds&quot;: 60,
  &quot;userCanToggleAutoRefresh&quot;: &quot;Yes&quot;,
  &quot;exportToPdfEnabled&quot;: &quot;Yes&quot;,
  &quot;exportToCsvEnabled&quot;: &quot;Yes&quot;,
  &quot;skipTemplateCheck&quot;: &quot;No&quot;,
  &quot;drillThroughEnabled&quot;: &quot;Yes&quot;,
  &quot;skipObjectPermissionCheck&quot;: &quot;No&quot;,
  &quot;serverScanMaxResults&quot;: 500,
  &quot;drillThroughMaxResults&quot;: 300,
  &quot;assignedTo&quot;: {
    &quot;users&quot;: &#x5B;],
    &quot;groups&quot;: &#x5B;&quot;Legal Team&quot;]
  },
  &quot;widgets&quot;: &#x5B;
    {
      &quot;id&quot;: &quot;&quot;,
      &quot;title&quot;: &quot;Active Contracts&quot;,
      &quot;type&quot;: &quot;kpiNumber&quot;,
      &quot;gridColumnSpan&quot;: 3,
      &quot;gridRowSpan&quot;: 2,
      &quot;query&quot;: {
        &quot;objectType&quot;: &quot;Document&quot;,
        &quot;class&quot;: &quot;Contract or Agreement&quot;,
        &quot;filters&quot;: &#x5B;
          {
            &quot;property&quot;: &quot;Effective through&quot;,
            &quot;operator&quot;: &quot;greaterOrEqual&quot;,
            &quot;value&quot;: &quot;@today&quot;,
            &quot;valueType&quot;: &quot;dateToken&quot;
          }
        ],
        &quot;aggregation&quot;: { &quot;type&quot;: &quot;summary&quot; }
      },
      &quot;display&quot;: { &quot;unit&quot;: &quot;contracts&quot; }
    },
    {
      &quot;id&quot;: &quot;&quot;,
      &quot;title&quot;: &quot;By Agreement Type&quot;,
      &quot;type&quot;: &quot;donut&quot;,
      &quot;gridColumnSpan&quot;: 9,
      &quot;gridRowSpan&quot;: 2,
      &quot;query&quot;: {
        &quot;objectType&quot;: &quot;Document&quot;,
        &quot;class&quot;: &quot;Contract or Agreement&quot;,
        &quot;aggregation&quot;: {
          &quot;type&quot;: &quot;groupByProperty&quot;,
          &quot;propertyName&quot;: &quot;Agreement type&quot;,
          &quot;resolveValueListLabels&quot;: &quot;Yes&quot;
        }
      }
    }
  ]
}
</pre></div>


<p class="wp-block-paragraph">This dashboard renders as a single KPI tile and a donut chart on the same row (3 + 9 = 12 columns), with auto-refresh every minute and access restricted to the Legal Team group. Nothing exotic, this is the kind of dashboard most customers should probably start with.</p>



<p class="wp-block-paragraph">If you wonder what it would look like, you can check the 1st and 2nd posts, they include these same widgets (though arranged slightly differently).</p>



<h2 class="wp-block-heading" id="h-8-what-comes-next">8. What comes next</h2>



<p class="wp-block-paragraph">In the next three posts (4a, 4b, 4c) I will go through the seven widget types one family at a time:</p>



<ul class="wp-block-list">
<li><strong>4a</strong> &#8211; Scalar widgets: <strong><em>kpiNumber</em></strong> and <strong><em>gauge</em></strong>.</li>



<li><strong>4b</strong> &#8211; Trend widgets: <strong><em>line</em></strong> and <strong><em>area</em></strong>.</li>



<li><strong>4c</strong> &#8211; Distribution and tabular widgets: <strong><em>donut</em></strong>, <strong><em>bar</em></strong>, <strong><em>table</em></strong>.</li>
</ul>



<p class="wp-block-paragraph">After the widget tour, Post 5 covers the query side (object types, classes, filters, date tokens) and Post 6 covers aggregations and reducers. By the end of those, you will have everything needed to write a non-trivial dashboard from scratch &#8211; if that&#8217;s what you want to do&#8230; Or you can just use the Visual Designer and follow along.</p>



<p class="wp-block-paragraph">In the meantime, I hope this anatomy post gives you a clean mental map. If something is unclear or if you spotted a field whose behavior I have not explained here, please reach out so I can update the post.</p>



<p class="wp-block-paragraph">Want to know more about this Business Dashboard? <a href="https://www.dbi-services.com/company/contact/" target="_blank" rel="noreferrer noopener">Contact us</a> and we will be happy to showcase it on <a href="https://www.m-files.com/" target="_blank" rel="noreferrer noopener">M-Files</a>.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-anatomy-of-a-dashboard-definition/">M-Files BD &#8211; Anatomy of a dashboard definition</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/m-files-bd-anatomy-of-a-dashboard-definition/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>M-Files BD &#8211; End-user experience</title>
		<link>https://www.dbi-services.com/blog/m-files-bd-end-user-experience/</link>
					<comments>https://www.dbi-services.com/blog/m-files-bd-end-user-experience/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Thu, 04 Jun 2026 20:48:21 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[Business Dashboard]]></category>
		<category><![CDATA[Charts]]></category>
		<category><![CDATA[CSV]]></category>
		<category><![CDATA[drill-through]]></category>
		<category><![CDATA[favorite]]></category>
		<category><![CDATA[Gauge]]></category>
		<category><![CDATA[KPI]]></category>
		<category><![CDATA[M-Files]]></category>
		<category><![CDATA[pdf]]></category>
		<category><![CDATA[widget]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=44932</guid>

					<description><![CDATA[<p>In the first post of this series, I introduced the Business Dashboard module I built, over the past few weeks, for M-Files. This second post is for the end user: what does it actually look like, where is it, and what can you do with it once your administrator has configured one or more dashboards [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-end-user-experience/">M-Files BD &#8211; End-user experience</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In the <a href="https://www.dbi-services.com/blog/m-files-introducing-the-business-dashboard-module/" id="44913" target="_blank" rel="noreferrer noopener">first post of this series</a>, I introduced the Business Dashboard module I built, over the past few weeks, for M-Files. This second post is for the end user: what does it actually look like, where is it, and what can you do with it once your administrator has configured one or more dashboards for you. No JSON in this post, no admin screens, no code. Just your experience as end-users. Please note that in the rest of this blog, when I say &#8220;administrator&#8221;, I mean the person that will design your dashboards. It might be a Super User or a System Admin, but he needs access to the M-Files Admin UI.</p>



<p class="wp-block-paragraph">If you have followed my posts for some time, you know I prefer to walk you through a feature (or an issue) exactly as you would discover it. So, let&#8217;s do the same here.</p>



<h2 id="h-1-where-to-find-the-dashboard-pane" class="wp-block-heading">1. Where to find the dashboard pane</h2>



<p class="wp-block-paragraph">The Business Dashboard adds a third tab to the right-hand pane of M-Files, next to the standard <strong>Metadata</strong> and <strong>Preview</strong> tabs. It is simply labelled <strong>Dashboard</strong>. Both M-Files Web (in a browser) and M-Files Desktop (the Windows client) show it &#8211; same panel, same behavior, with one platform-specific detail I will flag along the way.</p>



<p class="wp-block-paragraph">As a reminder, this is what it looks like and how to access it:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c31c12aa&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c31c12aa" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="2560" height="1954" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-3-scaled.png" alt="M-Files home page, with the Business Dashboard panel opened showing one dashboard with multiple widgets" class="wp-image-45092" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-3-scaled.png 2560w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-3-300x229.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-3-1024x782.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-3-768x586.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-3-1536x1172.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-3-2048x1563.png 2048w" sizes="auto, (max-width: 2560px) 100vw, 2560px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<p class="wp-block-paragraph">If you do not see the Dashboard tab, two things are possible: either the module is not installed in your vault, or it is installed but your vault is configured to use the old UI. In both cases, the next step is to ask your administrator.</p>



<p class="wp-block-paragraph">A small note on the pane width: the right pane is resizable by dragging its left edge. The dashboard AND the widgets inside respond to that resize. Therefore, on very narrow widths, widgets like donuts will have their legends move from the side to the bottom, to give more space to the charts. Don&#8217;t hesitate to play with the resize, it can be a good stress-reliever ;).</p>



<h2 id="h-2-selecting-and-switching-between-dashboards" class="wp-block-heading">2. Selecting and switching between dashboards</h2>



<p class="wp-block-paragraph">At the top of the pane, you have a <strong>&#8220;Current dashboard:&#8221;</strong> dropdown. It lists every dashboard your administrator has decided you can see, in the order the administrator chose. There might be more of them, but in this case, you don&#8217;t have permissions to see them. Click the dropdown, pick a dashboard, the panel updates immediately.</p>



<p class="wp-block-paragraph">The last dashboard you selected is remembered for the next time you open the pane, so if you mostly use one dashboard (e.g. the Contracts one), you do not need to re-select it every morning. More on that &#8220;last seen&#8221; vs &#8220;favorite&#8221; vs &#8220;link&#8221; below, in following sections.</p>



<p class="wp-block-paragraph">Under the dropdown, if your administrator added a description to the dashboard, you will see a thin italic strip that contains it. It is clamped to two lines maximum. If the description is longer, hover the strip to see the full text in a tooltip. In addition to that, when the dashboard dropdown is opened, you will also be able to see the description of the dashboard you hover over, which will have a darker background.</p>



<p class="wp-block-paragraph">In this screenshot, you can see the selected dashboard is about <strong>Contracts</strong> and its description is <strong>Quick view of contracts: active, &#8230;</strong>, while the description of the <strong>Invoices Overview</strong> dashboard is <strong>Purchase and Sales Invoice breakdown by year, workflow state, and customer.</strong>. Therefore, before loading it, you can get a quick idea on what exactly it will contain:</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c31c1be8&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c31c1be8" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="1908" height="1478" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.2-1.png" alt="A close up on the dashboard dropdown selector and description" class="wp-image-45095" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.2-1.png 1908w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.2-1-300x232.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.2-1-1024x793.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.2-1-768x595.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.2-1-1536x1190.png 1536w" sizes="auto, (max-width: 1908px) 100vw, 1908px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h2 id="h-3-the-widget-grid" class="wp-block-heading">3. The widget grid</h2>



<p class="wp-block-paragraph">Each dashboard is a grid of widgets. As said, the grid is responsive and it contains 12 columns, so each widget can use from 1/12th and up to 100% of the width. Your administrator decides which widgets appear, in what order, at what size.</p>



<p class="wp-block-paragraph">As of now, there are seven widget types supported:</p>



<figure class="wp-block-table"><table><thead><tr><th>Widget</th><th>What it shows</th></tr></thead><tbody><tr><td><strong>KPI Number</strong></td><td>A single value, typically a count, a sum (e.g. &#8220;Active contracts: 142&#8221;, &#8220;Invoices: 1&#8217;520&#8217;430 CHF&#8221;) or other mathematical operators like min/max/average/median. But it can only display a date (with min/max).</td></tr><tr><td><strong>Gauge</strong></td><td>A dial with a value against a defined range. Some gauges are numeric (e.g. overdue invoices against a target of 0-50). Some are in <strong>date mode</strong>, showing a date value as a day offset from today (&#8220;days until earliest contract expiry&#8221;).</td></tr><tr><td><strong>Donut chart</strong></td><td>A distribution across categories or time periods (e.g. contracts by agreement type). Slice values appear inline. The legend is shown next to the donut when the pane is wide enough, otherwise below. Also supports multi-series by displaying multiple sub-donuts.</td></tr><tr><td><strong>Bar chart</strong></td><td>A distribution across categories or time periods, displayed either vertically or horizontally. Also supports multi-series, with stacked or grouped bars.</td></tr><tr><td><strong>Line chart</strong></td><td>A distribution across categories or time periods, with data points linked to each other. Also supports multi-series, with multiple lines.</td></tr><tr><td><strong>Area chart</strong></td><td>Same data as a line, with the area below the line filled, to emphasise volume.</td></tr><tr><td><strong>Table</strong></td><td>A sortable list of objects or groups of objects, with pagination and additional columns that can be added. </td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Each widget has a title at the top, sometimes a short italic subtitle (the widget description, when your administrator added one), and three small elements in the top-right corner of the widget header that I will detail in the next sections.</p>



<h2 id="h-4-refreshing-the-data" class="wp-block-heading">4. Refreshing the data</h2>



<p class="wp-block-paragraph">Data on the dashboard is fetched live from M-Files. There are three ways the dashboard can be refreshed:</p>



<ul class="wp-block-list">
<li><strong>Auto-refresh.</strong> If your administrator enabled auto-refresh, the panel re-queries all widgets at a configured interval (e.g. every five minutes). If your administrator also enabled the user toggle, you see an <strong>Auto-refresh: On / Off</strong> switch in the top bar that lets you pause and resume it. If the user toggle is disabled, the switch will be greyed-out.</li>



<li><strong>Global manual refresh.</strong> A small <strong><em>↻</em></strong> button in the top bar refreshes every widget at once. To avoid accidental hammering, the button enters a 15 second cooldown after a click. The per-widget refresh buttons are also locked during this cooldown.</li>



<li><strong>Per-widget refresh.</strong> Each widget has its own <strong><em>↻</em></strong> button in its top-right corner. Click it to refresh that widget alone. A 15 second cooldown applies to the clicked widget only.</li>
</ul>



<p class="wp-block-paragraph">Each widget header also shows a faint <strong>&#8220;X ago&#8221;</strong> or <strong>&#8220;just now&#8221;</strong> text indicating when the data was last successfully loaded. Hover it to see the exact timestamp in a tooltip: <strong>&#8220;<em>Last refreshed: hh:mm:ss</em>&#8220;</strong>. This is useful to spot, at a glance, if all widgets were successfully refreshed or if there is one that is fresher than another (typically because you refreshed it manually).</p>



<h2 id="h-5-drill-through-exploring-the-objects-behind-the-numbers" class="wp-block-heading">5. Drill-through: exploring the objects behind the numbers</h2>



<p class="wp-block-paragraph">This is one of the features that makes the difference between an &#8220;interesting dashboard&#8221; and an &#8220;actually useful solution&#8221;. If your administrator enabled drill-through on the dashboard, all widgets automatically become clickable, and clicking them opens a <strong>detail modal</strong> listing the underlying M-Files objects.</p>



<p class="wp-block-paragraph">What is clickable:</p>



<figure class="wp-block-table"><table><thead><tr><th>Element</th><th>What it shows</th></tr></thead><tbody><tr><td>KPI tile</td><td>All the objects matching that widget&#8217;s query.</td></tr><tr><td>Gauge face</td><td>All the objects matching the widget&#8217;s query before applying the reducer (e.g. the minimum date is displayed, but all objects are shown in the drill-through).</td></tr><tr><td>Donut slice</td><td>Objects in that specific category or time period.</td></tr><tr><td>Bar column</td><td>Same as donut.</td></tr><tr><td>Line point</td><td>Same as donut.</td></tr><tr><td>Area point</td><td>Same as donut.</td></tr><tr><td>Row in a <strong><em>list</em></strong> table</td><td>No modal; clicking an object name navigates directly to it</td></tr><tr><td>Row in other tables</td><td>Same as donut.</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">The modal that opens shows a table with one column for the object name (as a clickable link &#8211; c.f. <strong><em>navigation</em></strong> section below), plus any additional columns your administrator configured. Columns are sortable: click a column header to sort ascending, click again for descending. Sorting applies across <strong>all fetched rows</strong>, not just the visible page. So if you sort an &#8220;Amount&#8221; column descending, you see the top 15 of all the rows that were retrieved, not just the page you happened to be on.</p>



<p class="wp-block-paragraph"><strong>Note:</strong> Sometimes the bar shows a small warning like <code>"(&#x26a0; 500 object(s) fetched)"</code>. This tells you the server retrieved up to its scan cap and there may be more objects in the vault that did not make it into the modal. Hover the <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> icon for an explanation. If you regularly hit that cap on a widget that should show everything, your administrator can raise (or disable) the cap on the dashboard configuration.</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c31c2796&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c31c2796" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="2560" height="1954" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.5-1-scaled.png" alt="A drill-through modal opened, showing the list of documents for a specific customer" class="wp-image-45093" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.5-1-scaled.png 2560w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.5-1-300x229.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.5-1-1024x782.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.5-1-768x586.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.5-1-1536x1172.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.5-1-2048x1563.png 2048w" sizes="auto, (max-width: 2560px) 100vw, 2560px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h3 id="h-why-drill-through-can-potentially-show-more-objects-than-the-widget-counts" class="wp-block-heading">Why drill-through can potentially show more objects than the widget counts</h3>



<p class="wp-block-paragraph">This deserves its own paragraph because it might be a bit counter-intuitive at first. The widget aggregation runs inside a capped scan (set by your administrator), while drill-through runs a focused search with the slice or row category as an additional filter. If the focused search can fit all matching objects under its own cap, you may see more objects in the modal than the widget counted.</p>



<p class="wp-block-paragraph">The <strong><em>Partial results</em></strong> badge on the widget header (when present) is the warning that the widget aggregate may be incomplete. There are basically two different limits. A first <strong>global</strong> limit, that ensures we do not request too much from the server and a second one linked to the <strong>drill-through</strong> that applies on filtered results.</p>



<p class="wp-block-paragraph"><strong><em>Example:</em></strong> A dashboard has been configured by your admin with 10&#8217;000 for the first limit and 5&#8217;000 for the second. Let&#8217;s take a <strong>Documents by Class</strong> donut widget (i.e. you want to see ALL documents &#8211; without any filters). If your vault has more than 10&#8217;000 objects of type <strong>Documents</strong>, then the widget would display 10&#8217;000 with a <strong><em>Partial results</em></strong> badge. In these first then thousand results, there might have been 150 <strong>Contract or Agreement</strong> (Class) shown in the donut. If you click on the donut slice for <strong>Contract or Agreement</strong>, the associated modal will open and it will show at least 150 and up to 5&#8217;000 results. In the end, if the initial search is <strong>&#8220;too wide&#8221;</strong>, you might hit a limit. But the drill-through will include another filter (here &#8220;Class=Contract or Agreement&#8221;), which only return these specific objects.</p>



<p class="wp-block-paragraph">Again, this is for performance reasons and as a kind of server-protection. If you have a real need, these limits can be increased or disabled completely by the admin &#8211; it is simply like that by default.</p>



<h2 id="h-6-navigating-to-an-object" class="wp-block-heading">6. Navigating to an object</h2>



<p class="wp-block-paragraph">In drill-through modals and in <strong><em>list</em></strong>-type table widgets, object names are blue underlined links. Clicking a link opens that object on the left-side of M-Files.</p>



<p class="wp-block-paragraph">A small detail that is sometimes useful: the drill-through modal <strong>stays open</strong> after you click a link, so you can navigate to several objects one after the other without losing your place in the list. Close the modal manually when you are done (using the <strong><em>×</em></strong> button, the Escape key, or clicking outside the modal).</p>



<figure class="wp-block-video"><video height="2164" style="aspect-ratio: 3296 / 2164;" width="3296" controls poster="http://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-2-scaled.png" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.6-1.mov"></video></figure>



<h2 id="h-7-exporting-to-pdf" class="wp-block-heading">7. Exporting to PDF</h2>



<p class="wp-block-paragraph">If your administrator enabled PDF export, a <strong><em>⎙ PDF</em></strong> button appears in the top bar of the dashboard. Clicking it generates a snapshot of the current dashboard state (charts as images, KPI numbers, tables) and <strong>opens a print dialog automatically</strong> (both on Web and Desktop). From there:</p>



<ul class="wp-block-list">
<li>Select &#8220;Save as PDF&#8221; (or &#8220;Microsoft Print to PDF&#8221;) as the printer and click Save.</li>



<li>Or pick a physical printer to print a hard copy.</li>
</ul>



<p class="wp-block-paragraph">The top bar, the refresh buttons and any open modals are excluded from the snapshot, so the PDF stays clean.</p>



<p class="wp-block-paragraph"><strong><em>Note 1:</em></strong> if your client blocks the automatic print dialog (a rare but possible restriction), an HTML file is downloaded instead. You can open that file in any browser and press Ctrl+P to print it in the exact same way.<br><strong><em>Note 2:</em></strong> the PDF export will respect exactly what M-Files is currently showing. This allows you to export the dashboard with a specific configuration. For example a table sorted in a different way that the default one, or some legends unselected from a donut, so that it ignores these details and re-render the donut with the remaining categories or time periods, etc. Whatever M-Files is showing will be what is present in the PDF export.</p>



<p class="wp-block-paragraph">A PDF export of a dashboard will look like that:</p>



<div data-wp-interactive="core/file" class="wp-block-file"><object data-wp-bind--hidden="!state.hasPdfPreview" hidden class="wp-block-file__embed" data="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/Contracts-Snapshot-01-Nov-2011.pdf" type="application/pdf" style="width:100%;height:600px" aria-label="Embed of Contracts Snapshot (01-Nov-2011)."></object><a id="wp-block-file--media-690988a8-2ead-4395-83b1-e4f1850ea708" href="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/Contracts-Snapshot-01-Nov-2011.pdf">Contracts Snapshot (01-Nov-2011)</a><a href="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/Contracts-Snapshot-01-Nov-2011.pdf" class="wp-block-file__button wp-element-button" download aria-describedby="wp-block-file--media-690988a8-2ead-4395-83b1-e4f1850ea708">Download</a></div>



<h2 id="h-8-exporting-to-csv" class="wp-block-heading">8. Exporting to CSV</h2>



<p class="wp-block-paragraph">Similarly to the PDF export, your administrator can also enable CSV export. When it is on, a <strong><em>⊞ CSV</em></strong> button appears in the header of every <strong><em>table</em></strong>, <strong><em>bar</em></strong>, <strong><em>donut</em></strong>, <strong><em>line</em></strong>, and <strong><em>area</em></strong> widget, as well as in the header of the drill-through modal for all widgets.</p>



<p class="wp-block-paragraph">Clicking <strong><em>⊞ CSV</em></strong> downloads a <strong><em>.csv</em></strong> file immediately. There is no extra server call; the export uses the data already in memory, so it is fast.</p>



<p class="wp-block-paragraph">The file starts with a small metadata block describing what the data represents / how it was fetched:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: xml; title: ; notranslate">
Widget Name,Active Contracts
Object Type,Document
Object Class,Contract or Agreement
Filter #1,Effective through greaterOrEqual @today
Aggregation Details,summary | count
Export Date,2026-06-04 19:30:08
</pre></div>


<p class="wp-block-paragraph">There might be two additional lines if the export is done on a specific group (a part of the initial results (e.g. after opening a donut&#8217;s slice modal details)) and if the widget is configured with multi-series. One line will describe which group is exported (category or time period) and another will describe the second dimension (the series).</p>



<p class="wp-block-paragraph">The above metadata section is followed by a blank separator row (empty line), then the actual data rows (column headers and associated values). All rows currently held in the browser are exported, not just the page you are looking at. For example, if the table shows page 2 only (objects 16-30) but 120 results were fetched, then all 120 rows will be in the CSV.</p>



<p class="wp-block-paragraph">The CSV file exported can then be opened in your preferred editor (Microsoft Excel, Euro-Office, LibreOffice, etc&#8230;). In the below image, I simply added the headers in Bold, but otherwise it&#8217;s the default outcome.</p>



<p class="wp-block-paragraph"><strong><em>Note:</em></strong> similarly to the PDF export, the CSV will also respect table sorting, so that the rows exported will be in the exact same order as the one that you see on your screen.</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c31c3b2b&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c31c3b2b" class="wp-block-image size-full is-style-default wp-lightbox-container"><img loading="lazy" decoding="async" width="1306" height="1175" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.8-1.png" alt="CSV export for a Purchase Invoices widget, showing the list of Purchase Invoices by Workflow state." class="wp-image-44939" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.8-1.png 1306w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.8-1-300x270.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.8-1-1024x921.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.8-1-768x691.png 768w" sizes="auto, (max-width: 1306px) 100vw, 1306px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h2 id="h-9-favorite-dashboard" class="wp-block-heading">9. Favorite dashboard</h2>



<p class="wp-block-paragraph">If you use one dashboard far more often than the others, you can pin it as your favorite. There is a small star button (<strong><em>☆</em></strong>) in the top bar:</p>



<ul class="wp-block-list">
<li>Select the dashboard you want to pin.</li>



<li>Click the <strong><em>☆</em></strong> button. It turns gold (<strong><em>★</em></strong>).</li>



<li>From now on, when you open the Dashboard pane, that dashboard is loaded automatically (instead of the last one you used).</li>
</ul>



<p class="wp-block-paragraph">To unpin, click the <strong><em>★</em></strong> again. The favorite is stored under your account, so it follows you across clients and devices. Do it once and you can then enjoy forever. Only one dashboard can be pinned at a time. Picking a different favorite simply replaces the previous one.</p>



<h2 id="h-10-sharing-a-dashboard-link" class="wp-block-heading">10. Sharing a dashboard link</h2>



<p class="wp-block-paragraph">Right next to the favorite button, a small link button (<strong><em><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /></em></strong>) can be used to share a link to a specific dashboard with someone else.</p>



<p class="wp-block-paragraph">As far as I know, this is the only feature &#8211; as of today &#8211; that behaves slightly differently between <strong>M-Files Web</strong> and <strong>M-Files Desktop</strong>:</p>



<ul class="wp-block-list">
<li>In <strong>M-Files Web</strong>, the clipboard will be updated to contain the <strong>current URL with the specific dashboard ID</strong>. If you give this link to someone else, they can open it in their Web and the same dashboard will be opened too. Access control still applies: if the recipient is not entitled to that dashboard, the link is silently ignored and their own default dashboard is shown instead.</li>



<li>In <strong>M-Files Desktop</strong>, the URL does exist, but it&#8217;s a random one specific to your Desktop. Therefore, it wouldn&#8217;t be usable by anybody else. Instead of sharing this URL, in Desktop, the link button will only copy the <strong>dashboard name</strong>. You can still paste the name in a chat or email and somebody else should be able to find the same dashboard, but manually&#8230;</li>
</ul>



<p class="wp-block-paragraph">It&#8217;s not super elegant, I know, but it&#8217;s how it works at the moment. I might re-work that later if I find a better way to do it or if there is a real need for it.</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c31c44f5&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c31c44f5" class="wp-block-image size-full wp-lightbox-container"><img loading="lazy" decoding="async" width="1908" height="1142" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.10-3.png" alt="Image showing the favorite gold star as well as the copy link feature." class="wp-image-45097" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.10-3.png 1908w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.10-3-300x180.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.10-3-1024x613.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.10-3-768x460.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.10-3-1536x919.png 1536w" sizes="auto, (max-width: 1908px) 100vw, 1908px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h2 id="h-11-indicators-worth-knowing" class="wp-block-heading">11. Indicators worth knowing</h2>



<p class="wp-block-paragraph">A few small visual indicators are worth recognising:</p>



<ul class="wp-block-list">
<li><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Partial results</strong> (orange badge in the widget header). The server scan cap was reached; the widget may not reflect the full data. Section 5 above has the full explanation.</li>



<li><strong>Grey placeholder text</strong> inside a widget. The dashboard definition has a small mis-configuration. The administrator was normally warned about it when he designed the dashboard but he probably ignored it.</li>



<li><strong>Red error overlay</strong> that replaces a widget. The server returned an unexpected error. The dashboard definition has a configuration problem. The dashboard designer part (admin) doesn&#8217;t allow to Save/Publish dashboards with such errors, but someone might have bypassed the administrative tool and updated the database directly.</li>



<li><strong>Spinning overlay</strong>. The widget is loading. If it stays in that state for a long time, the vault may be under load. Use the per-widget refresh button to retry once the spinner disappears. I haven&#8217;t seen the spinner stays more than 2 seconds myself, but I guess it could happen.</li>



<li><strong>Numbers higher than what you see in your views</strong>. Dashboard widgets can use either user-level permissions (default) or server-level permissions, depending on the configuration done by the administrator. Some use-cases might require to give access to all M-Files users to some KPIs for example. In this case, a dashboard could use server-level permissions, so that the KPIs are accurate and the same for everybody, even people with no access to the underlying objects. The navigation (links) to the objects will always respect your actual user-level permissions, obviously. So the only thing that the dashboard can give you access to, if configured in that way by the administrator, is KPIs or some charts (possibly even without the drill-through &#8211; so you don&#8217;t have any details about the objects themselves).</li>
</ul>



<h2 id="h-12-what-comes-next" class="wp-block-heading">12. What comes next</h2>



<p class="wp-block-paragraph">What I believe is genuinely useful about this module, is that it removes the small friction of &#8220;I just want to know quickly&#8221;. Opening a Power BI report takes a context switch; opening a saved view, scrolling and counting takes time. The Business Dashboard sits one click away in the M-Files window the user is already in, and it shows the answer he is looking for with nice visuals.</p>



<p class="wp-block-paragraph">In the next post of this series, I will move to the other side of the panel: how an administrator configures a dashboard. I will start with the <strong>anatomy of a dashboard definition</strong> &#8211; the JSON structure that drives everything you just saw.</p>



<p class="wp-block-paragraph">Want to know more about this Business Dashboard? <a href="https://www.dbi-services.com/company/contact/" target="_blank" rel="noreferrer noopener">Contact us</a> and we will be happy to showcase it on <a href="https://www.m-files.com/" target="_blank" rel="noreferrer noopener">M-Files</a>.</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-bd-end-user-experience/">M-Files BD &#8211; End-user experience</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/m-files-bd-end-user-experience/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.6-1.mov" length="24820520" type="video/quicktime" />

			</item>
		<item>
		<title>M-Files &#8211; Introducing the Business Dashboard module</title>
		<link>https://www.dbi-services.com/blog/m-files-introducing-the-business-dashboard-module/</link>
					<comments>https://www.dbi-services.com/blog/m-files-introducing-the-business-dashboard-module/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Tue, 02 Jun 2026 19:50:32 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[Business Dashboard]]></category>
		<category><![CDATA[Charts]]></category>
		<category><![CDATA[Gauge]]></category>
		<category><![CDATA[KPI]]></category>
		<category><![CDATA[M-Files]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=44913</guid>

					<description><![CDATA[<p>Some time ago, a customer asked whether it was possible, in M-Files, to have dashboards with KPIs and a global overview of what was happening in their vault. It was a topic I had in my radar for a few years already, but I never had &#8211; or took &#8211; the time to really work [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-introducing-the-business-dashboard-module/">M-Files &#8211; Introducing the Business Dashboard module</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Some time ago, a customer asked whether it was possible, in M-Files, to have dashboards with KPIs and a global overview of what was happening in their vault. It was a topic I had in my radar for a few years already, but I never had &#8211; or took &#8211; the time to really work on it. I jumped on the occasion to start building a solution in parallel of my daily-job at dbi services. Several evenings (who said nights?), weekends and some holidays later, here we are.</p>



<p class="wp-block-paragraph">The starting point was a real observation: the views of M-Files are powerful, the metadata model is flexible, the workflows are solid, but <strong>getting an at-a-glance picture of the business is still a clicking exercise</strong>. You open a view, you count, you open another view, you count again, you switch to the search, you start building an Excel with what you found&#8230; However, you might still not have the chart you wanted to present in the management meeting that starts in ten minutes.</p>



<p class="wp-block-paragraph">This blog post introduces what I called the <strong>Business Dashboard module</strong>, a custom M-Files extension designed and built to fill that gap. This is the first post of a series that will walk through the module. From the end-user experience, to the authoring side, and finally to a complete worked example. I will use the M-Files <strong>Sample Vault</strong> for this series since it contains test data. You can quickly and easily deploy that vault via the <a href="https://product.m-files.com/downloads/" target="_blank" rel="noreferrer noopener">M-Files installer</a>.</p>



<h2 id="h-1-the-problem-to-solve" class="wp-block-heading">1. The problem to solve</h2>



<p class="wp-block-paragraph">As you might know, M-Files ships with a few visualization features (views, search facets, the right-hand Metadata and Preview panes). With these, you can answer one of the following questions, but never all at once and never at a single glance:</p>



<ul class="wp-block-list">
<li>How many contracts are currently active in the vault?</li>



<li>How many of them expire in the next 30 days?</li>



<li>What is the distribution of our contracts by agreement type?</li>



<li>What is the total amount of invoices waiting to be approved, broken down by customer?</li>



<li>How many documents was each employee responsible for last quarter?</li>
</ul>



<p class="wp-block-paragraph">These are not exotic questions. They are the kind of things you might face every week. And the honest answer, today, is &#8220;I will get back to you later&#8221;. Sometimes there is a Power BI dashboard plugged into the vault SQL backend that answers some of them, but that is a second tool, a second login, often a second team to involve, and usually a permission/access story that does not match the per-user M-Files context.</p>



<p class="wp-block-paragraph">I wanted something simpler. Something that lives <strong>inside M-Files</strong>, that integrates with the M-Files access model, that an administrator can configure without writing any code, and that the end user can open with one click from within M-Files.</p>



<h2 id="h-2-what-the-business-dashboard-module-is" class="wp-block-heading">2. What the Business Dashboard module is</h2>



<p class="wp-block-paragraph">The Business Dashboard is an additional <strong>panel</strong> that appears on the right-hand side of M-Files (alongside Metadata and Preview). It works in both M-Files Web and Desktop (latest UI). End users pick a dashboard to render, which contains <strong>widgets</strong> (tiles) that display live business data from the vault using KPIs, donut, bar, line, area, tables or gauges. Data is fetched live from M-Files and it can refresh itself. It provides multiple other features such as export to PDF or CSV, favorites, links, even object navigation, etc&#8230; From a security point of view, the access control will be in Post 8.</p>



<p class="wp-block-paragraph">For administrators, dashboards are defined as <strong>JSON documents</strong> edited inside a dedicated tab in M-Files Admin. The editor offers both a JSON editor and a <strong>Visual Designer</strong> that lets you build dashboards without any knowledge of what JSON is. Definitions are stored in the vault, so a backup includes them automatically and there is no extra database to provision or manage.</p>



<p class="wp-block-paragraph">Two design choices are worth pulling out here, because they shape everything else in the series:</p>



<ul class="wp-block-list">
<li><strong>The engine is fully generic.</strong> There is no hardcoded business concept anywhere in the code. The same module works for contracts, invoices, customers, employees, projects, or whatever objects is contained within your vault. The JSON tells the engine <strong>how to query</strong> (which object type, which class, which property to group by, which filter to apply), not <strong>what values to expect</strong>.</li>



<li><strong>The wire format is frozen.</strong> Once the dashboard JSON is saved, the calls between the UI and the server speak a fixed shape. This means that when/if I ship a new widget type or a new aggregation, existing dashboards will keep working with no migration.</li>
</ul>



<p class="wp-block-paragraph">This is what the <strong>Business Dashboard</strong> looks like, from end-user point of view (click to enlarge the image):</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a301c31d52a3&quot;}" data-wp-interactive="core/image" data-wp-key="6a301c31d52a3" class="wp-block-image size-full is-style-default wp-lightbox-container"><img loading="lazy" decoding="async" width="2560" height="1954" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-4-scaled.png" alt="M-Files Business Dashboard" class="wp-image-45099" srcset="https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-4-scaled.png 2560w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-4-300x229.png 300w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-4-1024x782.png 1024w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-4-768x586.png 768w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-4-1536x1172.png 1536w, https://www.dbi-services.com/blog/wp-content/uploads/sites/2/2026/06/2.1-4-2048x1563.png 2048w" sizes="auto, (max-width: 2560px) 100vw, 2560px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button></figure>



<h2 id="h-3-concrete-use-cases" class="wp-block-heading">3. Concrete use cases</h2>



<p class="wp-block-paragraph">Rather than list features, let me describe four concrete scenarios where the <strong>Business Dashboard</strong> could shine:</p>



<ul class="wp-block-list">
<li><strong>Contracts overview.</strong> A KPI tile showing the number of active contracts, another showing those expiring in the next 30 days, a donut chart by agreement type, a bar chart showing expiries per month for the next year (e.g. until end of year or next 365 days), and a list of contracts to review this week. One panel, no clicking, refreshed automatically.</li>



<li><strong>Purchase invoices.</strong> Total amount waiting for approval, oldest invoice still pending, distribution by customer/countries/regions, a gauge of overdue invoices against a target of zero.</li>



<li><strong>Employees and responsibilities.</strong> A list of employees by department, with the count of documents worked on by each one in the past week. Possibly the list of people in holidays with their substitute. Useful for capacity planning and for the &#8220;who is on vacation, who covers what&#8221; conversation.</li>



<li><strong>Customer activity.</strong> Documents by customer in the last quarter, broken down by document class. Quickly spot the customers who went quiet and the ones who suddenly became very active.</li>
</ul>



<p class="wp-block-paragraph">None of these are hardcoded in the module. They are <strong>examples of what an administrator can configure</strong>, once, using the Visual Designer or the JSON editor. The same engine produces all of them and any number of variations.</p>



<p class="wp-block-paragraph"><strong>Note:</strong> Whatever the widget used, with at most one more click, you can get the details of which objects correspond to what you are looking for. For example, if you have a KPI for the number of active contracts, if it shows 25, then simply clicking on that 25 will open a <strong>drill-through</strong> with the list of all 25 objects in that specific situation, with optional additional details. See the follow-up posts for more details on that (and all other features).</p>



<h2 id="h-4-where-it-runs" class="wp-block-heading">4. Where it runs</h2>



<p class="wp-block-paragraph">The module contains two parts:</p>



<ul class="wp-block-list">
<li><strong><em>BusinessDashboard</em></strong> &#8211; the server-side, which contains the query engine, the validator, the persistence layer, etc.</li>



<li><strong><em>BusinessDashboard.UIX</em></strong> &#8211; the client-side, which contains all the display part for the end-users.</li>
</ul>



<p class="wp-block-paragraph">Both are installed, independently, in the M-Files Admin, via the standard application installation process. In regards to compatibility, it should technically supports all recent versions of M-Files that uses the new UI. I personally tested it extensively on M-Files 26.3 / 26.4 / 26.5.</p>



<p class="wp-block-paragraph">No external service is contacted; everything runs inside the vault. This makes the module a good fit for both on-premises and cloud deployments (though for cloud, the module would need to first be validated by M-Files), or for any deployment where adding a second tool (Power BI, Grafana) is not desirable (too complex to setup, too much for the actual need, or simply for security reasons).</p>



<h2 id="h-5-what-this-module-is-not" class="wp-block-heading">5. What this module is not</h2>



<p class="wp-block-paragraph">I think it is fair, before closing this introduction, to flag what the Business Dashboard module is <strong>not</strong> trying to do.</p>



<ul class="wp-block-list">
<li>It is <strong>not a BI tool</strong>. No SQL access, no data warehouse, no cross-vault joins. The engine only queries the M-Files vault it is deployed on and that&#8217;s it. If you need to combine data from a vault and an ERP, a dedicated BI stack might still be the right answer. <strong>UNLESS</strong> you are already merging the ERP data in M-Files (external repository or database connector). In that case, the Business Dashboard would be able to query it as well.</li>



<li>It is <strong>not (really) a reporting engine</strong>. No scheduled report, no email delivery. However, you can export a dashboard as PDF and you can export a widget (or its drill-through) to CSV (Excel-compatible).</li>



<li>It is <strong>not (really) designed for very large aggregations</strong>. The engine fetches up to a configurable amount of objects per widget (500 by default &#8211; can be increased, decreased or disabled completely&#8230;). Then aggregates it in memory, which is fine for any reasonable business question. Though it is not fine for things such as &#8220;list all 50 million objects that were created in the last 10 years&#8221;&#8230; If the limit is hit, the user knows about it with the help of a special badge.</li>



<li>It is <strong>not a replacement for M-Files views</strong> (or is it&#8230;?). Depending on use-cases, views can still be a better starting point. The Business Dashboard sits next to them, not in front of them.</li>
</ul>



<p class="wp-block-paragraph">If your need fits inside those boundaries, the module might make your life easier, irrespective of whether you are a decision maker, a power user, a simple user or an administrator. If it does not, please get in touch and I will be happy to discuss what would!</p>



<h2 id="h-6-what-comes-next-in-the-series" class="wp-block-heading">6. What comes next in the series</h2>



<p class="wp-block-paragraph">This series is going to cover, in order:</p>



<ul class="wp-block-list">
<li><strong><a href="https://www.dbi-services.com/blog/m-files-bd-end-user-experience/" id="44932" target="_blank" rel="noreferrer noopener">Post 2</a></strong> &#8211; The <strong>end-user experience</strong>: opening the panel, switching dashboards, drill-through, exports, favorites, sharing.</li>



<li><strong><a href="https://www.dbi-services.com/blog/m-files-bd-anatomy-of-a-dashboard-definition/" id="44952" target="_blank" rel="noreferrer noopener">Post 3</a></strong> &#8211; The anatomy of a <strong>dashboard definition</strong>: the JSON structure that drives everything.</li>



<li><strong>Posts <a href="https://www.dbi-services.com/blog/m-files-bd-scalar-widgets-kpinumber-and-gauge/" id="45018" target="_blank" rel="noreferrer noopener">4a</a>, <a href="https://www.dbi-services.com/blog/m-files-bd-trend-widgets-line-and-area/" id="45076" target="_blank" rel="noreferrer noopener">4b</a>, 4c</strong> &#8211; The <strong>widget catalog</strong>, split by type family: scalar widgets (<strong><em>kpiNumber</em></strong>, <strong><em>gauge</em></strong>), trend widgets (<strong><em>line</em></strong>, <strong><em>area</em></strong>), and distribution / tabular widgets (<strong><em>donut</em></strong>, <strong><em>bar</em></strong>, <strong><em>table</em></strong>).</li>



<li><strong>Post 5</strong> &#8211; Writing queries: <strong><em>objectType</em></strong>, <strong><em>class</em></strong>, <strong>filters</strong>, date tokens.</li>



<li><strong>Post 6</strong> &#8211; <strong>Aggregations</strong> and <strong>reducers</strong>.</li>



<li><strong>Post 7</strong> &#8211; The <strong>Admin tab</strong>: Visual Designer, JSON editor, validation, import / export.</li>



<li><strong>Post 8</strong> &#8211; Access control, performance settings, interactive features.</li>



<li><strong>Posts 9a and 9b</strong> &#8211; The same Contracts dashboard built two ways: through the <strong>Visual Designer</strong> (9a) and through the <strong>JSON editor</strong> (9b), on the M-Files Sample Vault so anyone can follow along.</li>
</ul>



<p class="wp-block-paragraph">If you have a specific question or a use case you would like to see covered in the series, don&#8217;t hesitate to reach out, and I might be able to add it to my to-do list.</p>



<p class="wp-block-paragraph">In the meantime, the next post will be a tour of what an end user sees the first time they click on the new Dashboard tab. See you there.</p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/m-files-introducing-the-business-dashboard-module/">M-Files &#8211; Introducing the Business Dashboard module</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/m-files-introducing-the-business-dashboard-module/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>OTDS &#8211; Installation of Replicas fail if the OT Admin password is too long?</title>
		<link>https://www.dbi-services.com/blog/otds-installation-of-replicas-fail-if-the-ot-admin-password-is-too-long/</link>
					<comments>https://www.dbi-services.com/blog/otds-installation-of-replicas-fail-if-the-ot-admin-password-is-too-long/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Mon, 30 Mar 2026 11:36:04 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[Documentum]]></category>
		<category><![CDATA[OTDS]]></category>
		<category><![CDATA[Password]]></category>
		<category><![CDATA[primary]]></category>
		<category><![CDATA[replica]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=43629</guid>

					<description><![CDATA[<p>For simplicity, in this blog, I will refer to the first OTDS instance as the &#8220;Primary&#8221; (the synchronization master host, installed with ISREPLICA_TOPOLOGY=0=FALSE) and any additional instances as &#8220;Replicas&#8221; (installed with ISREPLICA_TOPOLOGY=1=TRUE). Over the past few years, I have installed and worked on around 20–30 different OTDS environments, some with a single instance and others [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/otds-installation-of-replicas-fail-if-the-ot-admin-password-is-too-long/">OTDS &#8211; Installation of Replicas fail if the OT Admin password is too long?</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">For simplicity, in this blog, I will refer to the first OTDS instance as the &#8220;<em><strong>Primary</strong></em>&#8221; (the synchronization master host, installed with <em><strong>ISREPLICA_TOPOLOGY=0=FALSE</strong></em>) and any additional instances as &#8220;<em><strong>Replicas</strong></em>&#8221; (installed with <em><strong>ISREPLICA_TOPOLOGY=1=TRUE</strong></em>). Over the past few years, I have installed and worked on around 20–30 different OTDS environments, some with a single instance and others with multiple instances (HA). Overall, it is not a bad piece of software, even though it could use improvements in certain areas (e.g.: c.f. <a href="https://www.dbi-services.com/blog/dctm-mfa-non-mfa-within-otds-based-on-target-applications/" target="_blank" rel="noreferrer noopener">this blog</a>). However, it was only after I started installing a few Replicas on recent OTDS versions (using a database backend instead of OpenDJ) that I encountered a rather unusual issue.</p>



<h2 class="wp-block-heading" id="h-1-otds-replica-installation-failure">1. OTDS Replica installation failure</h2>



<p class="wp-block-paragraph">Single-instance installations using the silent properties file were always successful, and most multi-instance installations worked as well. However, I encountered a very specific issue twice: the Primary instance would install successfully, but the Replica installation would fail with an error stating &#8220;<em><strong>parameter JDBC_CONNECTION_STRING not defined</strong></em>&#8220;. Since everything runs in automated environments (Kubernetes or Ansible), I knew it was not a human error. When comparing the silent properties files, everything looked correct. The file used on the Primary was exactly the same as the one used on the Replica, except for &#8220;<em><strong>ISREPLICA_TOPOLOGY=0</strong></em>&#8221; and &#8220;<em><strong>ENCRYPTION_KEY=</strong></em>&#8221; on the Primary versus &#8220;<em><strong>ISREPLICA_TOPOLOGY=1</strong></em>&#8221; and &#8220;<em><strong>ENCRYPTION_KEY=XXXXXXX</strong></em>&#8221; on the Replica.</p>



<p class="wp-block-paragraph">This is the expected configuration. A Replica needs to take the value of &#8220;<em><strong>directory.bootstrap.CryptSecret</strong></em>&#8221; from the &#8220;<em><strong>otds.properties</strong></em>&#8221; file of the Primary and use that value for &#8220;<em><strong>ENCRYPTION_KEY</strong></em>&#8220;. Therefore, when you install the Primary instance, the value remains empty because nothing is installed yet. During the Replica installation, the automation retrieves this value and populates the parameter accordingly. But then why would the Primary installation succeed while the Replica fails when using the exact same silent properties file? Quite strange, right? First of all, I tried running the installer manually (outside of Kubernetes or Ansible) to see whether additional details would appear in the console:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;tomcat@otds-1 workspace_otds]$ /app/scripts/workspace_otds/otds/setup -qbi -rf /app/scripts/workspace_otds/otds/silent.properties

OpenText Directory Services 24.4.0

Error, parameter JDBC_CONNECTION_STRING not defined.
&#x5B;tomcat@otds-1 workspace_otds]$
</pre></div>


<h2 class="wp-block-heading" id="h-2-going-further-into-the-installation-logs">2. Going further into the installation logs</h2>



<p class="wp-block-paragraph">The generated log file was not really helpful either:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [10,11,12,21,27,28,42,43]; title: ; notranslate">
&#x5B;tomcat@otds-1 workspace_otds]$ cat otds.log
...
2025-08-08  6:38:40 chmod ran successfully on /etc/opentext/unixsetup
2025-08-08  6:38:40 Setting environment variable &quot;ACTION&quot; to &quot;-1&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;UPGRADE&quot; to &quot;0&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;PATCH&quot; to &quot;0&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;INSTALLED&quot; to &quot;0&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;INSTALLEDVERSION&quot; to &quot;0.0.0&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;PRODUCTINSTANCE&quot; to &quot;1&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;PRODUCTVERSION&quot; to &quot;24.4.0.4503&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;PRODUCTNAME&quot; to &quot;OpenText Directory Services&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;PRODUCTID&quot; to &quot;OTDS&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;PATCHVERSION&quot; to &quot;0&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;ROOTUSER&quot; to &quot;0&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;Main_INSTALLED&quot; to &quot;-1&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;INST_GROUP&quot; to &quot;tomcat&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;INST_USER&quot; to &quot;tomcat&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;INSTALL_DIR&quot; to &quot;/app/tomcat/app_data/otds&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;TOMCAT_DIR&quot; to &quot;/app/tomcat&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;PRIMARY_FQDN&quot; to &quot;otds-1.otds.otdsdev.svc.cluster.local&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;ISREPLICA_TOPOLOGY&quot; to &quot;1&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;IMPORT_DATA&quot; to &quot;0&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;OTDS_PASS&quot; to &quot;*****&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;ENCRYPTION_KEY&quot; to &quot;mqLgucZ8UIUnNcLwjwmhNw==&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;MIGRATION_OPENDJ_URL&quot; to &quot;&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;MIGRATION_OPENDJ_PASSWORD&quot; to &quot;*****&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;JDBC_CONNECTION_STRING&quot; to &quot;&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;JDBC_USERNAME&quot; to &quot;&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;JDBC_PASSWORD&quot; to &quot;*****&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;ACTION&quot; to &quot;3&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;Main_ACTION&quot; to &quot;3&quot; : Success
2025-08-08  6:38:40 Adding Pre-req &quot;TOMCAT7_HIGHER&quot;
...
2025-08-08  6:38:40 Action #1 ended: OK
2025-08-08  6:38:40 Setting environment variable &quot;PRIMARY_FQDN&quot; to &quot;otds-1.otds.otdsdev.svc.cluster.local&quot; : Success
2025-08-08  6:38:40 Setting environment variable &quot;ISREPLICA_TOPOLOGY&quot; to &quot;1&quot; : Success
2025-08-08  6:38:40 Skipping IMPORT_DATA parameter (condition is false)
2025-08-08  6:38:40 Skipping OTDS_PASS parameter (condition is false)
2025-08-08  6:38:40 Setting environment variable &quot;ENCRYPTION_KEY&quot; to &quot;mqLgucZ8UIUnNcLwjwmhNw==&quot; : Success
2025-08-08  6:38:40 Skipping MIGRATION_OPENDJ_URL parameter (condition is false)
2025-08-08  6:38:40 Skipping MIGRATION_OPENDJ_PASSWORD parameter (condition is false)
2025-08-08  6:38:40 Error, parameter JDBC_CONNECTION_STRING not defined.
2025-08-08  6:38:40 Setup Ended: 1
2025-08-08  6:38:40 ============= Verbose logging Ended =============
&#x5B;tomcat@otds-1 workspace_otds]$
</pre></div>


<p class="wp-block-paragraph">For reference, here is the content of the &#8220;silent.properties&#8221; file that this Replica installation uses:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [19,25,26]; title: ; notranslate">
&#x5B;tomcat@otds-1 workspace_otds]$ cat otds/silent.properties
&#x5B;Setup]
Id=OTDS
Version=24.4.0.4503
Patch=0
Basedir=/app/scripts/workspace_otds/otds
Configfile=/app/scripts/workspace_otds/otds/setup.xml
Action=Install
Log=/app/scripts/workspace_otds/otds/otds.log
Instance=1
Feature=All

&#x5B;Property]
INST_GROUP=tomcat
INST_USER=tomcat
INSTALL_DIR=/app/tomcat/app_data/otds
TOMCAT_DIR=/app/tomcat
PRIMARY_FQDN=otds-1.otds.otdsdev.svc.cluster.local
ISREPLICA_TOPOLOGY=1
IMPORT_DATA=0
OTDS_PASS=m1z6GX+HEX81DRpC
ENCRYPTION_KEY=mqLgucZ8UIUnNcLwjwmhNw==
MIGRATION_OPENDJ_URL=
MIGRATION_OPENDJ_PASSWORD=
JDBC_CONNECTION_STRING=jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=db_host.domain.com)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=db_svc.domain.com)))
JDBC_USERNAME=OTDS
JDBC_PASSWORD=Shu#Asd#Tgb;6799
&#x5B;tomcat@otds-1 workspace_otds]$
</pre></div>


<p class="wp-block-paragraph">(These are the real passwords from that environment. I have changed them since then, obviously, but I included them so you can understand the details below. The encryption key is altered, though &#8211; the system originally took the real one from the &#8220;Primary&#8221; instance.)</p>



<h2 class="wp-block-heading" id="h-3-status-of-the-installer-created-managed-files">3. Status of the installer created/managed files</h2>



<p class="wp-block-paragraph">After the failure, I checked the parameter file that the OTDS installer populates during installation, but it was mostly empty and not yet filled:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [10,16,19,25,31,34]; title: ; notranslate">
&#x5B;tomcat@otds-1 workspace_otds]$ cat /etc/opentext/unixsetup/OTDS_parameters_1.txt

#GROUP name that should be used to change file group ownership (group of USER)
INST_GROUP=tomcat

#USER name that should be used to change file ownership (user running processes)
INST_USER=tomcat

#Specify the installation directory for OpenText Directory Services
INSTALL_DIR=/usr/local/OTDS

#Specify the directory, where (64-bit) Apache Tomcat 10 or higher is installed
TOMCAT_DIR=/app/tomcat

#This hostname is used by other instances to connect to the synchronization master host.
PRIMARY_FQDN=

#Is this server a supplementary instance to an existing environment?
ISREPLICA_TOPOLOGY=0

#Specify OpenDJ connection for import.
IMPORT_DATA=0

#Specify the data encryption key from an existing instance.
ENCRYPTION_KEY=

#OpenDJ LDAP URL (example: ldap://localhost:1389)
MIGRATION_OPENDJ_URL=

#Specify JDBC connection String (example: jdbc:postgresql://localhost:5432/postgres). NOTE: Enter these values carefully since they cannot be validated here. Refer to the OTDS installation and administration guide for JDBC URL samples for supported databases.
JDBC_CONNECTION_STRING=

#Specify Database User Name
JDBC_USERNAME=
&#x5B;tomcat@otds-1 workspace_otds]$
</pre></div>


<p class="wp-block-paragraph">Finally, the &#8220;<strong><em>otds.properties</em></strong>&#8221; file (normally generated during the installation) was also not present yet:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;tomcat@otds-1 workspace_otds]$ ls -l $APP_DATA/otds/config/otds.properties
ls: cannot access &#039;/app/tomcat/app_data/otds/config/otds.properties&#039;: No such file or directory
&#x5B;tomcat@otds-1 workspace_otds]$
</pre></div>


<h2 class="wp-block-heading" id="h-4-attempts-to-debug-the-issue">4. Attempts to debug the issue</h2>



<p class="wp-block-paragraph">I tried launching the installer multiple times, on that OTDS Replica, while making small changes to the silent properties file to see if something specific would cause it to fail. Starting by modifying the &#8220;<em><strong>JDBC_CONNECTION_STRING</strong></em>&#8221; parameter, since that is what the installer complained about, but without success. I then suspected the password parameter. Because passwords are masked in the logs (&#8220;<strong>*</strong>&#8220;), it is impossible to see whether the value is parsed correctly or not.</p>



<p class="wp-block-paragraph">Therefore, I replaced the OTDS Admin password in the silent properties file with &#8220;<em><strong>dummyPassword</strong></em>&#8220;, and the installer suddenly proceeded further&#8230; I cancelled the installation because this was not the real password of the &#8220;<em><strong>otadmin</strong></em>&#8221; account on the Primary instance, but in this case the &#8220;<em><strong>JDBC_CONNECTION_STRING</strong></em>&#8221; parameter was no longer empty and the installer continued normally.</p>



<p class="wp-block-paragraph"><em><strong>Note:</strong></em> the OTDS documentation specifies that passwords must contain at least eight characters, including one lowercase letter, one uppercase letter, one number, and one special character. However, it appears that this rule may not be strictly validated during Replica installations (and possibly not for the Primary either?).</p>



<p class="wp-block-paragraph">At that point it became clear that the password itself was involved in the issue, somehow. Looking at the script &#8220;<em><strong>tools/setup.sh</strong></em>&#8220;, you can see that the installer extracts the value of &#8220;<em><strong>OTDS_PASS</strong></em>&#8220;, applies a function called &#8220;<em><strong>AsctoHex</strong></em>&#8220;, and then encrypts it. My original &#8220;<em><strong>otadmin</strong></em>&#8221; password was 16 characters long and satisfied all complexity requirements. However, I noticed that the password contained the string &#8220;<em><strong>HEX</strong></em>&#8220;. Since the installer converts the password to hexadecimal before encryption, I wondered whether the presence of the string &#8220;<em><strong>HEX</strong></em>&#8221; might interfere with this process. That would be quite unbelievable, right?</p>



<h2 class="wp-block-heading" id="h-5-a-problem-with-the-password-length-or-content">5. A problem with the password length or content?</h2>



<p class="wp-block-paragraph">To test this idea, I removed the &#8220;<em><strong>E</strong></em>&#8221; in the middle, transforming &#8220;<em><strong>HEX</strong></em>&#8221; into &#8220;<em><strong>HX</strong></em>&#8221; and effectively reducing the password length by one character:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;tomcat@otds-1 workspace_otds]$ grep OTDS_PASS otds/silent.properties | awk -F= &#039;{print $2}&#039; | wc -c
17
&#x5B;tomcat@otds-1 workspace_otds]$ # 17 means 16 char since wc -c count the new line in this command
&#x5B;tomcat@otds-1 workspace_otds]$
&#x5B;tomcat@otds-1 workspace_otds]$ sed -i &#039;s,HEX,HX,&#039; otds/silent.properties
&#x5B;tomcat@otds-1 workspace_otds]$
&#x5B;tomcat@otds-1 workspace_otds]$ grep OTDS_PASS otds/silent.properties | awk -F= &#039;{print $2}&#039; | wc -c
16
&#x5B;tomcat@otds-1 workspace_otds]$ # 16 means 15 char now
</pre></div>


<p class="wp-block-paragraph">To re-execute the installer after a failure, you must remove the content of the &#8220;<em><strong>/etc/opentext</strong></em>&#8221; directory (which kind of caches the content from the &#8220;<em><strong>silent.properties</strong></em>&#8221; file) and also delete the &#8220;<em><strong>otds.properties</strong></em>&#8221; file if it exists (not in my case):</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;tomcat@otds-1 workspace_otds]$ rm -rf /etc/opentext/*
&#x5B;tomcat@otds-1 workspace_otds]$
</pre></div>


<p class="wp-block-paragraph">In addition to modifying the &#8220;<em><strong>silent.properties</strong></em>&#8221; file, I also changed the &#8220;<em><strong>otadmin</strong></em>&#8221; password through the OTDS otds-admin UI (see the OTDS Install &amp; Admin Guide, section 7.2.5 &#8220;Resetting a user password&#8221;). Then I started a new Replica installation to see whether changing &#8220;<em><strong>HEX</strong></em>&#8221; to &#8220;<em><strong>HX</strong></em>&#8221; from the password would resolve the issue:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;tomcat@otds-1 workspace_otds]$ /app/scripts/workspace_otds/otds/setup -qbi -rf /app/scripts/workspace_otds/otds/silent.properties

OpenText Directory Services 24.4.0

------------------------------------------------------------------------------
  OpenText Directory Services
------------------------------------------------------------------------------
Installing OpenText Directory Services Component
Please wait .
Installation of OpenText Directory Services Component OK

Installation completed. Results:

OpenText Directory Services Component OK

Installation finished.

&#x5B;tomcat@otds-1 workspace_otds]$
</pre></div>


<p class="wp-block-paragraph">… It worked …?</p>



<p class="wp-block-paragraph">If the issue was really caused by the presence of &#8220;<em><strong>HEX</strong></em>&#8221; in the password, then replacing it with &#8220;<em><strong>HXE</strong></em>&#8221; should also work, right? Unfortunately, when I tried that, the issue came back&#8230; This indicates that the real problem is not the literal &#8220;<em><strong>HEX</strong></em>&#8221; string but maybe something related to password length, complexity, or how the installer processes and encrypts the password internally?</p>



<h2 class="wp-block-heading" id="h-6-conclusion">6. Conclusion</h2>



<p class="wp-block-paragraph">In the end, I reverted to the shorter 15-character password that worked and prepared all higher environments at this customer to use 15-character passwords. This approach worked without issue for five additional environments until, of course, it failed again, in Production…</p>



<p class="wp-block-paragraph">Since it failed in another environment even with a 15-character password, the length alone does not seem to be the root cause. When reviewing previously installed environments across multiple customers, I found a few instances running with &#8220;<em><strong>otadmin</strong></em>&#8221; passwords of up to 19 characters long (about 111/120 bits of entropy according to a password manager like KeePass). This is significantly stronger than the 15-character password (96 bits) used in the Production environment where the issue occurred.</p>



<p class="wp-block-paragraph">Therefore, since I couldn&#8217;t find any logical reasons why the issue happened on some environments but not with others, I opened a ticket with OpenText. I described everything and we went through several weeks of exchanges to try to find an explanation but without success. As of today, I still don&#8217;t know why ~10% of the OTDS Replicas that I installed faced an issue with the OT Admin password, but the fix, was simply to change the password in the UI and start the silent installation again. I don&#8217;t have an environment to test / debug that issue anymore, since it&#8217;s not easily reproducible. Guess I will need to wait for next time to get more debug logs from the OTDS installer (&#8220;<em><strong>-debug</strong></em>&#8221; option). In the meantime, I can only assume something is probably wrong in the way OTDS manages the password or its hash.</p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/otds-installation-of-replicas-fail-if-the-ot-admin-password-is-too-long/">OTDS &#8211; Installation of Replicas fail if the OT Admin password is too long?</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/otds-installation-of-replicas-fail-if-the-ot-admin-password-is-too-long/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Dctm &#8211; Another DM_LICENSE_E_INVALID_LICENSE error but caused by JMS this time</title>
		<link>https://www.dbi-services.com/blog/dctm-another-dm_license_e_invalid_license-error-but-caused-by-jms-this-time/</link>
					<comments>https://www.dbi-services.com/blog/dctm-another-dm_license_e_invalid_license-error-but-caused-by-jms-this-time/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Sun, 22 Mar 2026 08:51:00 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[D2]]></category>
		<category><![CDATA[DM_LICENSE_E_INVALID_LICENSE]]></category>
		<category><![CDATA[Documentum]]></category>
		<category><![CDATA[jms]]></category>
		<category><![CDATA[Proxy]]></category>
		<category><![CDATA[SSO]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=43521</guid>

					<description><![CDATA[<p>At the end of last year, I published a first blog about a DM_LICENSE_E_INVALID_LICENSE error in D2 SSO login through OTDS. The root cause in that previous post was a duplicate user with one lowercase and one uppercase user_login_name. However, I did mention that there can be several reasons for that error. In this blog, [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/dctm-another-dm_license_e_invalid_license-error-but-caused-by-jms-this-time/">Dctm &#8211; Another DM_LICENSE_E_INVALID_LICENSE error but caused by JMS this time</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">At the end of last year, I published a <a href="https://www.dbi-services.com/blog/dctm-d2-sso-through-otds-fails-with-dm_license_e_invalid_license/" target="_blank" rel="noreferrer noopener">first blog</a> about a DM_LICENSE_E_INVALID_LICENSE error in D2 SSO login through OTDS. The root cause in that previous post was a duplicate user with one lowercase and one uppercase <strong><em>user_login_name</em></strong>. However, I did mention that there can be several reasons for that error. In this blog, I will describe another such case.</p>



<h2 class="wp-block-heading" id="h-1-symptoms-in-d2-logs">1. Symptoms in D2 logs</h2>



<p class="wp-block-paragraph">The generated D2 logs associated with this new issue are almost exactly the same. The only difference is that the Repository returns &#8220;<em><strong>null</strong></em>&#8221; as the userid (<em><strong>user_name</strong></em>). See the message &#8220;<em><strong>Authentication failed for user null with docbase REPO_NAME</strong></em>&#8220;. This wasn&#8217;t the case in the other blog post:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [4,5,15,16,17,21,23,24,25,26,27,28,29,30,31,32,33,34,72]; title: ; notranslate">
&#x5B;tomcat@d2-0 logs]$ cat D2.log
...
2025-12-08 12:21:14,784 UTC &#x5B;INFO ] (https-jsse-nio-8080-exec-47) - c.emc.x3.portal.server.X3HttpSessionListener  : Created http session 8531D373A3EA12A398B158AF656E7D20
2025-12-08 12:21:14,784 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : OTDS : No user name on the Http session yet
2025-12-08 12:21:14,785 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : OTDS : No access_token found in Http request or Cookie Redirecting to OTDS Server
2025-12-08 12:21:14,786 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Identified scheme : https
2025-12-08 12:21:14,786 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Identified server name : d2.domain.com
2025-12-08 12:21:14,787 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Identified server port : 443
2025-12-08 12:21:14,787 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Built server host is : https://d2.domain.com:443
2025-12-08 12:21:14,788 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] header name=Host, value=d2.domain.com
2025-12-08 12:21:14,789 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] MaxHeaderValueSize: 8192
2025-12-08 12:21:14,792 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] validating the input valued2.domain.com
2025-12-08 12:21:14,793 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Identified host : d2.domain.com
2025-12-08 12:21:14,794 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Overall base URL built : https://d2.domain.com/D2
2025-12-08 12:21:14,795 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : Redirection url post encoding - https%3A%2F%2Fd2.domain.com%2FD2%2Fd2_otds.html%3ForigUrl%3D%2FD2%2F
2025-12-08 12:21:14,797 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : OTDS : OAUTH final login sendRedirect URL : https://otds-mfa.domain.com/otdsws/oauth2/auth?response_type=token&amp;client_id=dctm-ns-d2&amp;redirect_uri=https%3A%2F%2Fd2.domain.com%2FD2%2Fd2_otds.html%3ForigUrl%3D%2FD2%2F&amp;logon_appname=Documentum+Client+CE+23.4
2025-12-08 12:21:14,798 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-47) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : OTDS : Sending redirection as it&#039;s not a rpc call : https://otds-mfa.domain.com/otdsws/oauth2/auth?response_type=token&amp;client_id=dctm-ns-d2&amp;redirect_uri=https%3A%2F%2Fd2.domain.com%2FD2%2Fd2_otds.html%3ForigUrl%3D%2FD2%2F&amp;logon_appname=Documentum+Client+CE+23.4
2025-12-08 12:21:15,018 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] MaxHeaderKeySize: 256
2025-12-08 12:21:15,018 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] MaxHeaderValueSize: 8192
2025-12-08 12:21:15,020 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : OTDS : No user name on the Http session yet
2025-12-08 12:21:15,021 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : OTDS : Found access_token on Http Cookie, invalidating the cookie by setting maxAge 0
2025-12-08 12:21:15,022 UTC &#x5B;INFO ] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : OTDS : setting the cookie as secure as its a https request
2025-12-08 12:21:15,024 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : OTDS : OTDS responded with a oauth token
2025-12-08 12:21:15,025 UTC &#x5B;INFO ] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : ------ Begin getUntrustedJwtHeader : eyJraWQiOiI1YjM4...oSD8Xh3vVmkekcA
2025-12-08 12:21:15,026 UTC &#x5B;INFO ] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : getUntrustedJwtHeader oauthTokenWithoutSignature : eyJraWQiOiI1YjM4...i1xYWN0LWQyIn0.
2025-12-08 12:21:15,614 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : ------ Begin validateOTDSTokenClaims : MYUSERID
2025-12-08 12:21:15,615 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  :  validateOTDSTokenClaims for user : MYUSERID , OTDS : currenttime: 1765196475615 expirationtime: 1765200074000
2025-12-08 12:21:15,615 UTC &#x5B;INFO ] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : ------ End validateOTDSTokenClaims : MYUSERID
2025-12-08 12:21:15,615 UTC &#x5B;INFO ] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : PublicKey for Key id : 5b38b...bf487 exists
2025-12-08 12:21:15,617 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  :  OTDS Deafault Repository from shiro configured : REPO_NAME
2025-12-08 12:21:15,617 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : OTDS : generating DM_Ticket for user : MYUSERID in Repository : REPO_NAME
2025-12-08 12:21:16,522 UTC &#x5B;ERROR] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : OTDS : OAuth Token Error occurred while generating a DCTM MultiUse Ticket for user  : MYUSERID
2025-12-08 12:21:16,522 UTC &#x5B;ERROR] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : OTDS : OAuth Token Error please validate the OTDS Config of user exists in Repository
com.documentum.fc.client.DfAuthenticationException: &#x5B;DM_SESSION_E_AUTH_FAIL]error:  &quot;Authentication failed for user null with docbase REPO_NAME.&quot;
        at com.documentum.fc.client.impl.docbase.DocbaseExceptionMapper.newException(DocbaseExceptionMapper.java:52)
        at com.documentum.fc.client.impl.connection.docbase.MessageEntry.getException(MessageEntry.java:39)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseMessageManager.getException(DocbaseMessageManager.java:137)
        at com.documentum.fc.client.impl.connection.docbase.netwise.NetwiseDocbaseRpcClient.checkForMessages(NetwiseDocbaseRpcClient.java:332)
        at com.documentum.fc.client.impl.connection.docbase.netwise.NetwiseDocbaseRpcClient.applyForObject(NetwiseDocbaseRpcClient.java:680)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection$8.evaluate(DocbaseConnection.java:1572)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.evaluateRpc(DocbaseConnection.java:1272)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.applyForObject(DocbaseConnection.java:1564)
        at com.documentum.fc.client.impl.docbase.DocbaseApi.authenticateUser(DocbaseApi.java:1894)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.authenticate(DocbaseConnection.java:460)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.open(DocbaseConnection.java:140)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.&lt;init&gt;(DocbaseConnection.java:109)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.&lt;init&gt;(DocbaseConnection.java:69)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnectionFactory.newDocbaseConnection(DocbaseConnectionFactory.java:32)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnectionManager.createNewConnection(DocbaseConnectionManager.java:202)
        at com.documentum.fc.client.impl.connection.docbase.DocbaseConnectionManager.getDocbaseConnection(DocbaseConnectionManager.java:132)
        at com.documentum.fc.client.impl.session.SessionFactory.newSession(SessionFactory.java:24)
        ...
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
        at java.base/java.lang.Thread.run(Thread.java:840)
2025-12-08 12:21:16,524 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : redirectToErrorPage : Redirecting to Error Page as Login failed for user : null and exception : {}
com.emc.x3.portal.server.filters.authc.X3OTDSAuthenticationFilter$1: Authentication failed for user null with repository REPO_NAME.
        at com.emc.x3.portal.server.filters.authc.X3OTDSAuthenticationFilter.validateTokenAndGetUserId(X3OTDSAuthenticationFilter.java:1167)
        at com.emc.x3.portal.server.filters.authc.X3OTDSAuthenticationFilter.onAccessDenied(X3OTDSAuthenticationFilter.java:293)
        at org.apache.shiro.web.filter.AccessControlFilter.onAccessDenied(AccessControlFilter.java:133)
        at org.apache.shiro.web.filter.AccessControlFilter.onPreHandle(AccessControlFilter.java:162)
        at org.apache.shiro.web.filter.PathMatchingFilter.isFilterChainContinued(PathMatchingFilter.java:223)
        at org.apache.shiro.web.filter.PathMatchingFilter.preHandle(PathMatchingFilter.java:198)
        ...
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
        at java.base/java.lang.Thread.run(Thread.java:840)
2025-12-08 12:21:16,524 UTC &#x5B;INFO ] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : Adding the LicenseException to the Session : DM_SESSION_E_AUTH_FAIL
2025-12-08 12:21:16,526 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Identified scheme : https
2025-12-08 12:21:16,526 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Identified server name : d2.domain.com
2025-12-08 12:21:16,526 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Identified server port : 443
2025-12-08 12:21:16,528 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Built server host is : https://d2.domain.com:443
2025-12-08 12:21:16,529 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] header name=Host, value=d2.domain.com
2025-12-08 12:21:16,530 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] MaxHeaderValueSize: 8192
2025-12-08 12:21:16,531 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] validating the input valued2.domain.com
2025-12-08 12:21:16,532 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Identified host : d2.domain.com
2025-12-08 12:21:16,533 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter : Overall base URL built : https://d2.domain.com/D2
2025-12-08 12:21:16,534 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-5) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  :  D2 redirecting to errorPage JSP  : https://d2.domain.com/D2/errors/authenticationError.jsp
2025-12-08 12:21:16,567 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-26) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] MaxHeaderKeySize: 256
2025-12-08 12:21:16,567 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-26) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] MaxHeaderValueSize: 8192
2025-12-08 12:21:16,568 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-26) - c.e.x.p.s.f.authc.X3OTDSAuthenticationFilter  : No LicenseExcepton found on HttpSession hence not Redirectling to License ErrorPage
2025-12-08 12:21:16,571 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-26) - c.e.x.p.s.f.a.X3TrustHttpAuthenticationFilter :  Selected Repository : REPO_NAME
2025-12-08 12:21:16,573 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-26) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] MaxHeaderKeySize: 256
2025-12-08 12:21:16,574 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-26) - o.o.e.logging.slf4j.Slf4JLogLevelHandlers$4   : &#x5B;EVENT SUCCESS -&gt; /D2/HTTPUtilities] MaxHeaderValueSize: 8192
2025-12-08 12:21:16,578 UTC &#x5B;INFO ] (https-jsse-nio-8080-exec-26) - c.emc.x3.portal.server.X3HttpSessionListener  : Expired Http session id : 8531D373A3EA12A398B158AF656E7D20
2025-12-08 12:21:16,578 UTC &#x5B;DEBUG] (https-jsse-nio-8080-exec-26) - com.emc.x3.server.context.ContextManager      : Create a new context manager
...
&#x5B;tomcat@d2-0 logs]$
</pre></div>


<h2 class="wp-block-heading" id="h-2-checking-the-repository-authentication-trace">2. Checking the Repository authentication trace</h2>



<p class="wp-block-paragraph">As usual, the next step is to check the Repository logs with the authentication trace enabled:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [4,9,12,13,14,15]; title: ; notranslate">
&#x5B;dmadmin@cs-0 ~]$ cat $DOCUMENTUM/dba/log/$DOCBASE_NAME.log
...
2025-12-08T12:21:16.235912      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  Entering RPC AUTHENTICATE_USER
2025-12-08T12:21:16.236052      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  Start Authentication : LOGON_NAME=MYUSERID, DOMAIN_NAME=, OS_LOGON_NAME=tomcat, OS_LOGON_DOMAIN=, ASSUME_USER=0, TRUSTED_LOGIN_ALLOWED=1, PRINCIPAL_AUTH=0, DO_SET_LOCALE=0, RECONNECT=0, CLIENT_TOKEN=&#x5B;-36, 8, 66, 12, 89, 102, -85, -11, 6, -115, -34, -68, -123, 11, 100]
2025-12-08T12:21:16.236115      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  Start Authenticate Client Instance
2025-12-08T12:21:16.236215      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  Start Verify Signature, Client : dfc_327WHMY40Mglbp4taDgajZEM39Lc , Host : d2-0.d2.dctm-ns.svc.cluster.local
2025-12-08T12:21:16.244603      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  End Verify Signature, Client : dfc_327WHMY40Mglbp4taDgajZEM39Lc , Host : d2-0.d2.dctm-ns.svc.cluster.local
2025-12-08T12:21:16.244657      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  End Authenticate Client Instance
2025-12-08T12:21:16.303325      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  Start-AuthenticateUser: ClientHost(d2-0.d2.dctm-ns.svc.cluster.local), LogonName(null), LogonOSName(tomcat), LogonOSDomain(), UserExtraDomain(), ServerDomain()
2025-12-08T12:21:16.303410      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  Start-AuthenticateUserName:
2025-12-08T12:21:16.303442      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  dmResolveNamesForCredentials: auth_protocol()
2025-12-08T12:21:16.305698      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  &#x5B;DM_USER_E_NOT_DOCUMENTUM_USER]error:  &quot;User null does not exist in the docbase&quot;
2025-12-08T12:21:16.305720      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  End-AuthenticateUserName: dm_user.user_login_domain(), Result: 0
2025-12-08T12:21:16.305730      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  Not Found dm_user.user_login_name(null), dm_user.user_login_domain()
2025-12-08T12:21:16.519331      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  Final Auth Result=F, LOGON_NAME=null, AUTHENTICATION_LEVEL=1, OS_LOGON_NAME=tomcat, OS_LOGON_DOMAIN=, CLIENT_HOST_NAME=d2-0.d2.dctm-ns.svc.cluster.local, CLIENT_HOST_ADDR=172.1.1.1, USER_LOGON_NAME_RESOLVED=1, AUTHENTICATION_ONLY=0, USER_NAME=, USER_OS_NAME=null, USER_LOGIN_NAME=null, USER_LOGIN_DOMAIN=, USER_EXTRA_CREDENTIAL&#x5B;0]=, USER_EXTRA_CREDENTIAL&#x5B;1]=, USER_EXTRA_CREDENTIAL&#x5B;2]=e2, USER_EXTRA_CREDENTIAL&#x5B;3]=, USER_EXTRA_CREDENTIAL&#x5B;4]=, USER_EXTRA_CREDENTIAL&#x5B;5]=, SERVER_SESSION_ID=0101234580c77e96, AUTH_BEGIN_TIME=Mon Dec  8 12:21:16 2025, AUTH_END_TIME=Mon Dec  8 12:21:16 2025, Total elapsed time=0 seconds
2025-12-08T12:21:16.519359      3567122&#x5B;3567122]        0101234580c77e96        &#x5B;AUTH]  Exiting RPC AUTHENTICATE_USER
...
&#x5B;dmadmin@cs-0 ~]$
</pre></div>


<p class="wp-block-paragraph">There is one thing that is quite strange in these logs. If you look at the beginning, it traces the authentication for &#8220;<strong><em>MYUSERID</em></strong>&#8220;. But then, in the middle of the process, that <em><strong>user_name</strong></em> becomes &#8220;<em><strong>null</strong></em>&#8220;. I do not recall seeing that behavior before, so I started investigating what might have caused it.</p>



<p class="wp-block-paragraph">The account &#8220;<em><strong>MYUSERID</strong></em>&#8221; existed in the Repository. This issue occurred on the same application as in the previous blog post, but this time in the TEST/QA environment (instead of DEV). The same OTDS and users were present, so my account was definitely there (without duplicates in TEST/QA).</p>



<h2 class="wp-block-heading" id="h-3-investigating-otds-authentication-logs">3. Investigating OTDS authentication logs</h2>



<p class="wp-block-paragraph">Since the <em><strong>dm_user</strong></em> object had a &#8220;<em><strong>user_source</strong></em>&#8221; of <em><strong>OTDS</strong></em>, I then checked the OTDS Authentication log file from the JMS. For this Documentum 23.4 version, the log file was &#8220;<em><strong>$JMS_HOME/logs/otdsauth.log</strong></em>&#8220;. Starting from version 25.4, this log file is located inside &#8220;<em><strong>$DOCUMENTUM/dba/log</strong></em>&#8221; instead:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [3,21]; title: ; notranslate">
&#x5B;dmadmin@cs-0 ~]$ cat $JMS_HOME/logs/otdsauth.log
...
2025-12-08 11:49:46,106 UTC ERROR &#x5B;] (https-jsse-nio-9082-exec-36) Thread&#x5B;https-jsse-nio-9082-exec-36,5,main] java.io.IOException: Unable to tunnel through proxy. Proxy returns &quot;HTTP/1.1 502 Bad Gateway&quot;
        at java.base/sun.net.www.protocol.http.HttpURLConnection.doTunneling0(HttpURLConnection.java:2311)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.doTunneling(HttpURLConnection.java:2181)
        at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.getOutputStream0(HttpURLConnection.java:1465)
        at java.base/sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1436)
        at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:220)
        at com.documentum.cs.otds.OTDSAuthenticationServlet.validatePassword(OTDSAuthenticationServlet.java:275)
        at com.documentum.cs.otds.OTDSAuthenticationServlet.doPost(OTDSAuthenticationServlet.java:175)
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
        ...
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.base/java.lang.Thread.run(Thread.java:840)

2025-12-08 12:21:16,302 UTC ERROR &#x5B;] (https-jsse-nio-9082-exec-50) Exception while fetching certificates from jwks url
&#x5B;dmadmin@cs-0 ~]$
</pre></div>


<p class="wp-block-paragraph">The first error message (11:49) occurred about 30 minutes before the authentication attempt. On the other hand, the last line (12:21) is directly linked to the problem according to its timestamp. This indicates that the Documentum Server was trying to fetch the <em><strong>JWKS certificate</strong></em>. This happens when the OTDS Authentication Servlet is configured with the &#8220;<em><strong>auto_cert_refresh=true</strong></em>&#8221; parameter (see the &#8220;<em><strong>otdsauth.properties</strong></em>&#8221; file).</p>



<p class="wp-block-paragraph">This forces the Documentum Server to contact the OTDS Server in order to retrieve the correct or current SSL certificate to use. However, that request failed. Even though it is not explicitly written, it is easy to deduce that the first error, related to a proxy communication issue, is the root cause.</p>



<h2 class="wp-block-heading" id="h-4-checking-newly-added-proxy-and-correcting-it">4. Checking newly added proxy and correcting it</h2>



<p class="wp-block-paragraph">As far as I knew, there should not have been any proxy configured on Documentum, since all components are internal to the customer and located within the same network. However, when checking the startup logs of the JMS, I noticed that a new proxy configuration had recently been added when the Tomcat process restarted less than two hours earlier:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [3,4,5,6]; title: ; notranslate">
&#x5B;dmadmin@cs-0 ~]$ grep proxy $JMS_HOME/logs/catalina.out
...
2025-12-08 10:54:56,385 UTC INFO &#x5B;main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dhttp.proxyHost=proxy.domain.com
2025-12-08 10:54:56,385 UTC INFO &#x5B;main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dhttp.proxyPort=2010
2025-12-08 10:54:56,385 UTC INFO &#x5B;main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dhttps.proxyHost=proxy.domain.com
2025-12-08 10:54:56,385 UTC INFO &#x5B;main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dhttps.proxyPort=2011
...
&#x5B;dmadmin@cs-0 ~]$
</pre></div>


<p class="wp-block-paragraph">After checking with the relevant teams, it turned out that this issue was not really related to Documentum itself. Someone had simply restarted the JMS after adding proxy settings as new JVM parameters while testing an external service that required internet access. Yes, directly in TEST/QA without validating in DEV first &#8211; it happens apparently.</p>



<p class="wp-block-paragraph">However, since no exceptions were configured through the <em><strong>no_proxy</strong></em> setting (&#8220;<em><strong>-Dhttp.nonProxyHosts</strong></em>&#8221; JVM parameter), it meant that 100% of the requests initiated by the JVM were forwarded to the proxy. That proxy had no knowledge of the OTDS server (which is expected), so the communication simply failed.</p>



<p class="wp-block-paragraph">After correcting the proxy configuration (either by removing it or by adding all internal domains to the <em><strong>no_proxy</strong></em> setting), the JVM was able to communicate with OTDS again. As a consequence, the D2 SSO started working successfully and the environment was back &#8220;online&#8221; for all testers. These two blog posts clearly demonstrate that just because D2 displays an error, it doesn&#8217;t mean that the real root cause is obvious. Careful investigation and analysis of the log files is always essential.</p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/dctm-another-dm_license_e_invalid_license-error-but-caused-by-jms-this-time/">Dctm &#8211; Another DM_LICENSE_E_INVALID_LICENSE error but caused by JMS this time</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/dctm-another-dm_license_e_invalid_license-error-but-caused-by-jms-this-time/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Dctm &#8211; IDS Source 16.7.5 config.bin crash during execution</title>
		<link>https://www.dbi-services.com/blog/dctm-ids-source-16-7-5-config-bin-crash-during-execution/</link>
					<comments>https://www.dbi-services.com/blog/dctm-ids-source-16-7-5-config-bin-crash-during-execution/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Tue, 10 Mar 2026 18:56:00 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[config.bin]]></category>
		<category><![CDATA[configurator]]></category>
		<category><![CDATA[crash]]></category>
		<category><![CDATA[Documentum]]></category>
		<category><![CDATA[ids]]></category>
		<category><![CDATA[Tomcat]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=43427</guid>

					<description><![CDATA[<p>Around six months ago, I faced a confusing issue with IDS Source 16.7.5 where the &#8220;config.bin&#8221; executable always crashed when I tried to run it. The installation of IDS binaries itself completed successfully without any errors. However, the configurator, which is supposed to set up the required objects inside the Repository, consistently crashed. 1. Environment [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/dctm-ids-source-16-7-5-config-bin-crash-during-execution/">Dctm &#8211; IDS Source 16.7.5 config.bin crash during execution</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Around six months ago, I faced a confusing issue with IDS Source 16.7.5 where the &#8220;<em><strong>config.bin</strong></em>&#8221; executable always crashed when I tried to run it. The installation of IDS binaries itself completed successfully without any errors. However, the configurator, which is supposed to set up the required objects inside the Repository, consistently crashed.</p>



<h2 class="wp-block-heading" id="h-1-environment-context-and-ids-upgrade">1. Environment context and IDS upgrade</h2>



<p class="wp-block-paragraph">This Documentum environment had just been upgraded to 23.4. The next step was to upgrade the associated IDS component. The latest version of IDS compatible with recent Documentum versions is 16.7.5.</p>



<p class="wp-block-paragraph">The execution of the &#8220;<em><strong>idsLinuxSuiteSetup.bin</strong></em>&#8221; installer properly extracted all binaries and deployed the WebCache application in its Tomcat server. To quickly verify that, you can check the version properties file and try starting/stopping the Tomcat instance of the IDS. On my side, there were no problems with that.</p>



<p class="wp-block-paragraph">To verify the installed version of IDS and ensure that the configurator was also updated:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [3,7,10]; title: ; notranslate">
&#x5B;dmadmin@cs-0 ~]$ cd $DM_HOME/webcache
&#x5B;dmadmin@cs-0 webcache]$
&#x5B;dmadmin@cs-0 webcache]$ cat version/version.properties
#Please don&#039;t remove this values
#Fri Oct 10 09:52:49 UTC 2025
INSTALLER_NAME=IDS
PRODUCT_VERSION=16.7.5
&#x5B;dmadmin@cs-0 webcache]$
&#x5B;dmadmin@cs-0 webcache]$ ls -l install/config.bin
-rwxrwxr-x 1 dmadmin dmadmin 54943847 Oct 19  2024 install/config.bin
&#x5B;dmadmin@cs-0 webcache]$
</pre></div>


<p class="wp-block-paragraph">The above confirms that WebCache was properly updated to version 16.7.5 on October 10. It also confirms that the &#8220;<em><strong>config.bin</strong></em>&#8221; is fairly recent (Q4 2024), i.e. much more recent that the old 16.7.4 file.</p>



<h2 class="wp-block-heading" id="h-2-running-the-ids-configurator-in-silent">2. Running the IDS configurator in silent</h2>



<p class="wp-block-paragraph">My next step was therefore to execute the configurator, still in silent mode, as I have done for all previous IDS installations and configurations. I have not written a blog about IDS silent installation yet, but I have done so for several other components. For example, you can refer to <a href="https://www.dbi-services.com/blog/documentum-silent-install-otds/" id="36492" target="_blank" rel="noreferrer noopener">this post</a> for the latest one published.</p>



<p class="wp-block-paragraph">The silent properties file for the IDS Source configurator is quite simple, as it only requires the Repository name to configure:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [7]; title: ; notranslate">
&#x5B;dmadmin@cs-0 webcache]$ cat ${install_file}
### Silent installation response file for IDS configurator
INSTALLER_UI=silent
KEEP_TEMP_FILE=true

### Configuration parameters
DOC_BASE_NAME=REPO_01

&#x5B;dmadmin@cs-0 webcache]$
</pre></div>


<p class="wp-block-paragraph">Initially, I simply executed &#8220;<em><strong>config.bin</strong></em>&#8220;. Since it crashed and there was absolutely nothing in the logs, I had to run it again with the <em><strong>DEBUG</strong></em> flag enabled:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [1,10,11]; title: ; notranslate">
&#x5B;dmadmin@cs-0 webcache]$ $DM_HOME/webcache/install/config.bin -DLOG_LEVEL=DEBUG -f ${install_file}
Preparing to install
Extracting the installation resources from the installer archive...
Configuring the installer for this system&#039;s environment...

Launching installer...

Picked up JAVA_TOOL_OPTIONS: -Djdk.util.zip.disableZip64ExtraFieldValidation=true -Djava.locale.providers=COMPAT,SPI --add-exports=java.base/sun.security.provider=ALL-UNNAMED --add-exports=java.base/sun.security.pkcs=ALL-UNNAMED --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-exports=java.base/sun.security.util=ALL-UNNAMED --add-exports=java.base/sun.security.tools.keytool=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED
&#x5B;dmadmin@cs-0 webcache]$
&#x5B;dmadmin@cs-0 webcache]$ echo $?
1
&#x5B;dmadmin@cs-0 webcache]$
</pre></div>


<h2 class="wp-block-heading" id="h-3-investigating-the-logs">3. Investigating the logs</h2>



<p class="wp-block-paragraph">As shown above, the execution failed, as the return code was &#8220;<em><strong>1</strong></em>&#8220;. With DEBUG enabled and after checking the generated files, I found the following:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
&#x5B;dmadmin@cs-0 webcache]$ find . -type f -mmin -20 -ls
92475248907    380 -rw-rw-r--   1  dmadmin  dmadmin    384222 Oct 10 11:58 ./install/logs/install.log
92470810541      4 -rw-rw-r--   1  dmadmin  dmadmin       219 Oct 10 11:57 ./install/installer.properties
92475252084     12 -rwxrwxrwx   1  dmadmin  dmadmin     10564 Oct 10 11:57 ./install/config_log/OpenText_Documentum_Interactive_Delivery_Services_Configuration_Install_10_10_2025_11_57_42.log
&#x5B;dmadmin@cs-0 webcache]$
&#x5B;dmadmin@cs-0 webcache]$ grep -iE &quot;_E_|_F_|ERROR|WARN|FATAL&quot; install/logs/install.log
TYPE ERROR_TYPE 0000000000000000 0 0 0
13:24:12,192 DEBUG &#x5B;main] com.documentum.install.shared.common.error.DiException - null/dba/config/GR_REPO/webcache.ini (No such file or directory)
13:24:12,193 DEBUG &#x5B;main] com.documentum.install.shared.common.error.DiException - null
13:24:12,194 DEBUG &#x5B;main] com.documentum.install.shared.common.error.DiException - null/dba/config/REPO_01/webcache.ini (No such file or directory)
13:24:12,194 DEBUG &#x5B;main] com.documentum.install.shared.common.error.DiException - null
13:24:12,199 DEBUG &#x5B;main] com.documentum.install.shared.common.error.DiException - null/dba/config/GR_REPO/webcache.ini (No such file or directory)
13:24:12,199 DEBUG &#x5B;main] com.documentum.install.shared.common.error.DiException - null
13:24:12,199 DEBUG &#x5B;main] com.documentum.install.shared.common.error.DiException - null/dba/config/REPO_01/webcache.ini (No such file or directory)
13:24:12,199 DEBUG &#x5B;main] com.documentum.install.shared.common.error.DiException - null
13:24:12,200 DEBUG &#x5B;main] com.documentum.install.shared.common.error.DiException - null/dba/config/REPO_01/webcache.ini (No such file or directory)
13:24:12,200 DEBUG &#x5B;main] com.documentum.install.shared.common.error.DiException - null
TYPE ERROR_TYPE 0000000000000000 0 0 0
&#x5B;dmadmin@cs-0 webcache]$
</pre></div>


<p class="wp-block-paragraph">The DEBUG logs above might make it look like the &#8220;<em><strong>$DOCUMENTUM</strong></em>&#8221; environment variable is missing, since it complains about &#8220;<em><strong>null/dba/xxx</strong></em>&#8221; not being found. However, that is not the issue. I checked all parameters and environment variables, and everything was configured correctly. In addition, Documentum had just been successfully upgraded from version 20.2 to 23.4 from start to finish, which confirmed that there was no problem with the OS or environment configuration. So I checked the second file:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [15,17,21,22,23,30]; title: ; notranslate">
&#x5B;dmadmin@cs-0 webcache]$ cat install/config_log/OpenText_Documentum_Interactive_Delivery_Services_Configuration_Install_10_10_2025_11_57_42.log

__________________________________________________________________________

Fri Oct 10 11:57:50 UTC 2025

Free Memory: 15947 kB
Total Memory: 49152 kB
...
Summary
-------

Installation: Successful with errors.

8 Successes
0 Warnings
1 NonFatalErrors
0 FatalErrors
...

Custom Action:            com.documentum.install.webcache.CustomActions.DiWAWebcsConfigureDocbase
                          Status: ERROR
                          Additional Notes: ERROR -     class com.documentum.install.webcache.CustomActions.DiWAWebcsConfigureDocbase.install() runtime exception:

...
====================STDERR ENTRIES==================

RepositoryManager: Trying fallback repository location...
8. final log file name=$DM_HOME/webcache/install/config_log/OpenText_Documentum_Interactive_Delivery_Services_Configuration_Install_10_10_2025_11_57_42.log
java.lang.NumberFormatException: For input string: &quot;&quot;
        at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
        at java.base/java.lang.Integer.parseInt(Integer.java:678)
        at java.base/java.lang.Integer.parseInt(Integer.java:786)
        at com.documentum.install.webcache.CustomActions.DiWAWebcsConfigureDocbase.configureDocbase(DiWAWebcsConfigureDocbase.java:329)
        at com.documentum.install.webcache.CustomActions.DiWAWebcsConfigureDocbase.install(DiWAWebcsConfigureDocbase.java:202)
        at com.zerog.ia.installer.actions.CustomAction.installSelf(Unknown Source)
        at com.zerog.ia.installer.InstallablePiece.install(Unknown Source)
        at com.zerog.ia.installer.InstallablePiece.install(Unknown Source)
        at com.zerog.ia.installer.GhostDirectory.install(Unknown Source)
        at com.zerog.ia.installer.InstallablePiece.install(Unknown Source)
        at com.zerog.ia.installer.Installer.install(Unknown Source)
        at com.zerog.ia.installer.LifeCycleManager.consoleInstallMain(Unknown Source)
        at com.zerog.ia.installer.LifeCycleManager.executeApplication(Unknown Source)
        at com.zerog.ia.installer.Main.main(Unknown Source)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:569)
        at com.zerog.lax.LAX.launch(Unknown Source)
        at com.zerog.lax.LAX.main(Unknown Source)
Execute Custom Code
    class com.documentum.install.webcache.CustomActions.DiWAWebcsConfigureDocbase.install() runtime exception:
java.awt.HeadlessException:
No X11 DISPLAY variable was set,
but this program performed an operation which requires it.
        at java.desktop/java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:164)
        at java.desktop/java.awt.Window.&lt;init&gt;(Window.java:553)
        at java.desktop/java.awt.Frame.&lt;init&gt;(Frame.java:428)
        at java.desktop/java.awt.Frame.&lt;init&gt;(Frame.java:393)
        at java.desktop/javax.swing.JFrame.&lt;init&gt;(JFrame.java:180)
        at com.documentum.install.webcache.CustomActions.DiWAWebcsConfigureDocbase.install(DiWAWebcsConfigureDocbase.java:215)
        at com.zerog.ia.installer.actions.CustomAction.installSelf(Unknown Source)
        at com.zerog.ia.installer.InstallablePiece.install(Unknown Source)
        at com.zerog.ia.installer.InstallablePiece.install(Unknown Source)
        at com.zerog.ia.installer.GhostDirectory.install(Unknown Source)
        at com.zerog.ia.installer.InstallablePiece.install(Unknown Source)
        at com.zerog.ia.installer.Installer.install(Unknown Source)
        at com.zerog.ia.installer.LifeCycleManager.consoleInstallMain(Unknown Source)
        at com.zerog.ia.installer.LifeCycleManager.executeApplication(Unknown Source)
        at com.zerog.ia.installer.Main.main(Unknown Source)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:569)
        at com.zerog.lax.LAX.launch(Unknown Source)
        at com.zerog.lax.LAX.main(Unknown Source)
Retrying Installables deferred in pass 0
Deferral retries done because:
There were no deferrals in the last pass.
8. final log file name=$DM_HOME/webcache/install/config_log/OpenText_Documentum_Interactive_Delivery_Services_Configuration_Install_10_10_2025_11_57_42.log
====================STDOUT ENTRIES==================
...
&#x5B;dmadmin@cs-0 webcache]$
</pre></div>


<p class="wp-block-paragraph">That log file appeared to indicate that a certain &#8220;<em><strong>Number</strong></em>&#8221; value might be missing (&#8220;<em><strong>NumberFormatException</strong></em>&#8220;). Without access to the IDS source code (and I always avoid de-compiling Documentum source files), it was impossible to determine exactly what was missing. There were no additional details in the logs, so in the end I had to reach out to OpenText support to find out what was causing the issue.</p>



<h2 class="wp-block-heading" id="h-4-root-cause-missing-value-for-tomcat-port-selected">4. Root cause: missing value for TOMCAT_PORT_SELECTED</h2>



<p class="wp-block-paragraph">After several back-and-forth exchanges and around 12 days of waiting for a solution, I finally received confirmation that this was a bug in the IDS Source 16.7.5 software. This version is the first one deployed on Tomcat instead of WildFly, so it was somewhat expected that some issues might appear.</p>



<p class="wp-block-paragraph">When installing the IDS Source binaries, the silent installation properties file requires you to define the port that Tomcat will use. This parameter is &#8220;<em><strong>USER_PORT_CHOICE=6677</strong></em>&#8220;. You can of course change the port if needed, but <em><strong>6677</strong></em> was the default port used with previous IDS versions running on WildFly, so I kept the same value when installing IDS 16.7.5 on Tomcat.</p>



<p class="wp-block-paragraph">The bug is that even though this value is used correctly during the Tomcat installation step, it is not properly written into the properties file that the configuration process later relies on. The IDS Source &#8220;<em><strong>config.bin</strong></em>&#8221; looks for the file &#8220;<em><strong>$DM_HOME/webcache/scs_tomcat.properties</strong></em>&#8221; and reads the port from the &#8220;<em><strong>TOMCAT_PORT_SELECTED</strong></em>&#8221; parameter.</p>



<p class="wp-block-paragraph">However, in IDS 16.7.5 this file is not updated during installation. As a result, the port value remains empty, which corresponds to the missing number referenced in the logs and causes the configurator to crash.</p>



<h2 class="wp-block-heading" id="h-5-fix-updating-scs-tomcat-properties">5. Fix: updating scs_tomcat.properties</h2>



<p class="wp-block-paragraph">The solution is fairly simple: manually update that file and run the configurator again. In my case, I used the HTTPS port <em><strong>6679</strong></em>, since my Tomcat was already in SSL (6677 + 2 = 6679):</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [2,5,14,15]; title: ; notranslate">
&#x5B;dmadmin@cs-0 webcache]$ port=6679
&#x5B;dmadmin@cs-0 webcache]$ sed -i &quot;s,\(TOMCAT_PORT_SELECTED=\).*,\1${port},&quot; $DM_HOME/webcache/scs_tomcat.properties
&#x5B;dmadmin@cs-0 webcache]$
&#x5B;dmadmin@cs-0 webcache]$
&#x5B;dmadmin@cs-0 webcache]$ $DM_HOME/webcache/install/config.bin -DLOG_LEVEL=DEBUG -f ${install_file}
Preparing to install
Extracting the installation resources from the installer archive...
Configuring the installer for this system&#039;s environment...

Launching installer...

Picked up JAVA_TOOL_OPTIONS: -Djdk.util.zip.disableZip64ExtraFieldValidation=true -Djava.locale.providers=COMPAT,SPI --add-exports=java.base/sun.security.provider=ALL-UNNAMED --add-exports=java.base/sun.security.pkcs=ALL-UNNAMED --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-exports=java.base/sun.security.util=ALL-UNNAMED --add-exports=java.base/sun.security.tools.keytool=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED
&#x5B;dmadmin@cs-0 webcache]$
&#x5B;dmadmin@cs-0 webcache]$ echo $?
0
&#x5B;dmadmin@cs-0 webcache]$
</pre></div>


<p class="wp-block-paragraph">As you can see above, the return code is now &#8220;<strong><em>0</em></strong>&#8220;, which indicates a successful execution. The logs generated during this new attempt are much cleaner, and there are no longer any exceptions or errors:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [14,20,21]; title: ; notranslate">
&#x5B;dmadmin@cs-0 webcache]$ cat install/config_log/OpenText_Documentum_Interactive_Delivery_Services_Configuration_Install_10_22_2025_13_37_46.log
__________________________________________________________________________

Wed Oct 22 01:39:40 UTC 2025

Free Memory: 14800 kB
Total Memory: 49152 kB
...
Summary
-------

Installation: Successful.

9 Successes
0 Warnings
0 NonFatalErrors
0 FatalErrors
...

Custom Action:            com.documentum.install.webcache.CustomActions.DiWAWebcsConfigureDocbase
                          Status: SUCCESSFUL

...
====================STDERR ENTRIES==================

RepositoryManager: Trying fallback repository location...
8. final log file name=$DM_HOME/webcache/install/config_log/OpenText_Documentum_Interactive_Delivery_Services_Configuration_Install_10_22_2025_13_37_46.log
Retrying Installables deferred in pass 0
Deferral retries done because:
There were no deferrals in the last pass.
8. final log file name=$DM_HOME/webcache/install/config_log/OpenText_Documentum_Interactive_Delivery_Services_Configuration_Install_10_22_2025_13_37_46.log
====================STDOUT ENTRIES==================
...
&#x5B;dmadmin@cs-0 webcache]$
</pre></div>


<p class="wp-block-paragraph">As mentioned earlier, this configurator is responsible for installing components inside the Repository. It creates the required IDS objects or updates them if they already exist. The DAR files were also successfully installed:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [2,18,19,20]; title: ; notranslate">
&#x5B;dmadmin@cs-0 webcache]$ iapi $DOCBASE_NAME -Udmadmin -Pxxx &lt;&lt; EOC
&gt; ?,c,select r_object_id, r_modify_date, object_name from dmc_dar order by r_modify_date asc;
&gt; EOC

        OpenText Documentum iapi - Interactive API interface
        Copyright (c) 2023. OpenText Corporation
        All rights reserved.
        Client Library Release 23.4.0000.0180

Connecting to Server using docbase REPO_01
&#x5B;DM_SESSION_I_SESSION_START]info:  &quot;Session 011234568027fb88 started for user dmadmin.&quot;

Connected to OpenText Documentum Server running Release 23.4.0000.0143  Linux64.Oracle
1&gt; 2&gt;
r_object_id       r_modify_date              object_name
----------------  -------------------------  ---------------------------------
...               ...                        ...
08123456800c99a5  10/22/2025 13:38:32        SCSDocApp
08123456800c99be  10/22/2025 13:38:58        SCSWorkflow
08123456800c99e1  10/22/2025 13:39:29        icmRating

(43 rows affected)
1&gt;
&#x5B;dmadmin@cs-0 webcache]$
</pre></div>


<h2 class="wp-block-heading" id="h-6-another-small-bug">6. Another small bug</h2>



<p class="wp-block-paragraph">However, I later discovered another small bug. The &#8220;<em><strong>scs_admin_config.product_version</strong></em>&#8221; attribute in the Repository was not updated correctly. Previously installed version was 16.7.4, so it&#8217;s unclear whether the configurator updated the value (with 16.7.4 still) or not at all. In any case, the stored product version was incorrect.</p>



<p class="wp-block-paragraph">This value is used by IDS to verify version compatibility during execution. For example, you can see this version referenced during the End-to-End tests. Therefore, I had to update the value manually. To correct the issue:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [2,3,4,21,27,34]; title: ; notranslate">
&#x5B;dmadmin@cs-0 webcache]$ iapi $DOCBASE_NAME -Udmadmin -Pxxx &lt;&lt; EOC
&gt; ?,c,select product_version from scs_admin_config;
&gt; ?,c,update scs_admin_config object set product_version=&#039;16.7.5&#039; where product_version=&#039;16.7.4&#039;;
&gt; ?,c,select product_version from scs_admin_config;
&gt; exit
&gt; EOC

        OpenText Documentum iapi - Interactive API interface
        Copyright (c) 2023. OpenText Corporation
        All rights reserved.
        Client Library Release 23.4.0000.0180

Connecting to Server using docbase REPO_01
&#x5B;DM_SESSION_I_SESSION_START]info:  &quot;Session 011234568027fd13 started for user dmadmin.&quot;

Connected to OpenText Documentum Server running Release 23.4.0000.0143  Linux64.Oracle
Session id is s0
API&gt;
product_version
------------------------
16.7.4
(1 row affected)

API&gt;
objects_updated
---------------
              1
(1 row affected)
&#x5B;DM_QUERY_I_NUM_UPDATE]info:  &quot;1 objects were affected by your UPDATE statement.&quot;

API&gt;
product_version
------------------------
16.7.5
(1 row affected)

API&gt; Bye
&#x5B;dmadmin@cs-0 webcache]$
</pre></div>


<p class="wp-block-paragraph">OpenText mentioned that both of these bugs should normally be fixed in a future update of the binaries. I have not checked in the last six months, but hopefully the issue has already been resolved. If not, at least you now have the information needed to fix it!</p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/dctm-ids-source-16-7-5-config-bin-crash-during-execution/">Dctm &#8211; IDS Source 16.7.5 config.bin crash during execution</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/dctm-ids-source-16-7-5-config-bin-crash-during-execution/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Dctm &#8211; Upgrade from 23.4 to 25.4 fails with DM_SERVER_E_SOCKOPT</title>
		<link>https://www.dbi-services.com/blog/dctm-upgrade-from-23-4-to-25-4-fails-with-dm_server_e_sockopt/</link>
					<comments>https://www.dbi-services.com/blog/dctm-upgrade-from-23-4-to-25-4-fails-with-dm_server_e_sockopt/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Sat, 07 Mar 2026 11:43:02 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[23.4]]></category>
		<category><![CDATA[25.4]]></category>
		<category><![CDATA[DM_SERVER_E_SOCKOPT]]></category>
		<category><![CDATA[Documentum]]></category>
		<category><![CDATA[repository]]></category>
		<category><![CDATA[SOCKOPT]]></category>
		<category><![CDATA[upgrade]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=43385</guid>

					<description><![CDATA[<p>Since its release in Q4 2025, I have worked on several upgrades from 23.4 to 25.4. The first one I worked on was for a customer using our own custom Documentum images. If you have followed my posts for some time, you might recall a previous blog post where I discussed a Documentum 20.2 to [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/dctm-upgrade-from-23-4-to-25-4-fails-with-dm_server_e_sockopt/">Dctm &#8211; Upgrade from 23.4 to 25.4 fails with DM_SERVER_E_SOCKOPT</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Since its release in Q4 2025, I have worked on several upgrades from 23.4 to 25.4. The first one I worked on was for a customer using our own custom Documentum images. If you have followed my posts for some time, you might recall a previous blog post where I discussed a Documentum 20.2 to 23.4 upgrade and some issues related to IPv6 being disabled (c.f. <a href="https://www.dbi-services.com/blog/documentum-23-4-doesnt-like-ipv6-being-disabled/" id="35783" target="_blank" rel="noreferrer noopener">this blog</a>).</p>



<h2 class="wp-block-heading" id="h-1-environment-details">1. Environment details</h2>



<p class="wp-block-paragraph">The source Documentum 23.4 environment was configured exactly like the one from that previous blog post, with the fix to ensure the components could start and run on IPv4 only. Using the exact same source code, I simply rebuilt the image with the same OS version (RHEL8 for Dctm 23.4). One difference was that I was previously running on a vanilla Kubernetes cluster, while for this blog I used RKE2 (from SUSE), meaning the OS and Kubernetes cluster were not the same.</p>



<p class="wp-block-paragraph">At the Documentum level, the Connection Broker was configured with &#8220;<em><strong>host=${Current-IPv4}</strong></em>&#8221; to force it to start with IPv4 only. The Repository had &#8220;<em><strong>ip_mode = V4ONLY</strong></em>&#8221; in its &#8220;<em><strong>server.ini</strong></em>&#8221; to achieve the same behavior. With these two configurations, the Documentum 23.4 environment was installed from scratch without any issues and was running properly. I performed several pod restarts over the next few days, and everything was running smoothly.</p>



<h2 class="wp-block-heading" id="h-2-upgrade-to-documentum-25-4">2. Upgrade to Documentum 25.4</h2>



<p class="wp-block-paragraph">Then it was time to upgrade the environment to 25.4. For that purpose, I built a new image on RHEL9 (switching from ubi8 to ubi9 for the base image). I had no problems with the preparation or the installation of the other binaries.</p>



<p class="wp-block-paragraph">I then triggered the upgrade process, which completed successfully for the Connection Broker. However, when it reached the Repository part, things were not that simple. The Repository upgrade log file contained the following:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; title: ; notranslate">
&#x5B;dmadmin@cs-0 ~]$ cat $DM_HOME/install/logs/install.log
20:30:29,056  INFO &#x5B;main] com.documentum.install.shared.installanywhere.actions.InitializeSharedLibrary - Log is ready and is set to the level - INFO
20:30:29,060  INFO &#x5B;main] com.documentum.install.shared.installanywhere.actions.InitializeSharedLibrary - The product name is: UniversalServerConfigurator
20:30:29,060  INFO &#x5B;main] com.documentum.install.shared.installanywhere.actions.InitializeSharedLibrary - The product version is: 25.4.0000.0143
20:30:29,060  INFO &#x5B;main]  -
20:30:29,089  INFO &#x5B;main] com.documentum.install.shared.installanywhere.actions.InitializeSharedLibrary - Done InitializeSharedLibrary ...
20:30:29,117  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWASilentCheckVaultStatus - Checking the vault status: Silent mode
20:30:29,117  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWASilentCheckVaultStatus - Checking whether vault enabled or not
20:30:29,121  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerInformation - Setting CONFIGURE_DOCBROKER value to TRUE for SERVER
20:30:29,121  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerInformation - Setting CONFIGURE_DOCBASE value to TRUE for SERVER
20:30:30,124  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckEnvrionmentVariable - The installer was started using the dm_launch_server_config_program.sh script.
20:30:30,124  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckEnvrionmentVariable - The installer will determine the value of environment variable DOCUMENTUM.
20:30:30,124  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckEnvrionmentVariable - existingVersion : 25.4serverMajorversion : 25.4
20:30:33,125  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckEnvrionmentVariable - The installer will determine the value of environment variable PATH.
20:30:33,125  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckEnvrionmentVariable - existingVersion : 25.4serverMajorversion : 25.4
20:30:36,126  INFO &#x5B;main]  - existingVersion : 25.4serverMajorversion : 25.4
20:30:36,136  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWASilentConfigurationInstallationValidation - Start to validate docbase parameters.
20:30:36,140  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerPatchExistingDocbaseAction - The installer will obtain all the DOCBASE on the machine.
20:30:38,146  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerDocAppFolder - The installer will obtain all the DocApps which could be installed for the repository.
20:30:38,148  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerLoadDocBaseComponentInfo - The installer will gather information about the component GR_REPO.
20:30:41,151  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerReadServerIniForLockbox - Lockbox disabled for the repository : GR_REPO.
20:30:41,153  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckKeepAEKUnchanged - vaule of isVaultEnabledinPrevious : null
20:30:41,156  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckKeystoreStatusForOld - The installer will check old AEK key status.
20:30:41,219  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckKeystoreStatus - Executed dm_crypto_create -check command and the return code - 1
20:30:41,221  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerLoadValidAEKs - AEK key type provided in properties is  : Local
20:30:41,266  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckKeystoreStatus - Executed dm_crypto_create -check command and the return code - 1
20:30:41,269  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerEnableLockBoxValidation - The installer will validate AEK fields.
20:30:41,272  INFO &#x5B;main]  - Is Vault enabled :false
20:30:41,273  INFO &#x5B;main]  - Is Vault enabled :false
20:30:41,326  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerValidateLockboxPassphrase - Installer will boot AEK key
20:31:11,376  INFO &#x5B;main]  - Is Vault enabled :false
20:31:11,377  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckKeystoreStatus - The installer will check keystore status.
20:31:11,419  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckKeystoreStatus - Executed dm_crypto_create -check command and the return code - 1
20:31:11,419  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckKeystoreStatus - The installer detected the keystore already exists and was created using user password.
20:31:11,425  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerQueryDatabaseInformation - The installer is gathering database connection information from the local machine.
20:31:11,427  INFO &#x5B;main] com.documentum.install.appserver.services.DiAppServerUtil - appServer == null
20:31:11,427  INFO &#x5B;main] com.documentum.install.appserver.services.DiAppServerUtil - isTomcatInstalled == true -- tomcat version is null
20:31:11,431  INFO &#x5B;main] com.documentum.install.appserver.tomcat.TomcatApplicationServer - setApplicationServer sharedDfcLibDir is:$DOCUMENTUM/dfc
20:31:11,431  INFO &#x5B;main] com.documentum.install.appserver.tomcat.TomcatApplicationServer - getFileFromResource for templates/appserver.properties
20:31:11,434  INFO &#x5B;main] com.documentum.install.appserver.tomcat.TomcatApplicationServer - Tomcat Home = $DOCUMENTUM/tomcat
20:31:11,438  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerModifyDocbaseDirectory - The installer will create the folder structure for repository GR_REPO.
20:31:11,440  INFO &#x5B;main]  - The installer will stop component process for GR_REPO.
20:31:11,479  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerUpdateTCSUnixServiceFile - The installer will check service entries for repository GR_REPO.
20:31:11,483  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerModifyDfcProperties - The installer will update dfc.properties file.
20:31:11,485  INFO &#x5B;main] com.documentum.install.shared.common.services.dfc.DiDfcProperties - Installer is not adding connection broker information as it is already added.
20:31:13,488  INFO &#x5B;main]  - The installer will update server.ini file for the repository.
20:31:13,493  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerDataIniGenerator - The installer will create data_dictionary.ini for the repository.
20:31:13,496  INFO &#x5B;main]  - The installer will obtain database server name for database dctmdb.
20:31:13,497  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerLoadServerWebcacheInfo - The installer will obtain database information of dctmdb.
20:31:13,498  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerWebCacheIniGenerator - The installer will update webcache.ini file for the repository.
20:31:13,503  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerTestDatabaseConnection4Docbase_Upgrade - The installer is testing the database connection information
20:31:13,503  INFO &#x5B;main] com.documentum.install.server.common.services.db.DiServerDbSvrOracleServer - The installer is validating the database version is supported.
20:31:13,638  INFO &#x5B;main] com.documentum.install.server.common.services.db.DiServerDbSvrOracleServer - The installer is validating the database connection information in the server.ini file.
20:31:13,910  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckIfDMSUpgraded - Check if upgrade of DMS tables is needed for current docbase.
20:31:13,911  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerCheckIfDMSUpgraded - The current repository doesn&#039;t need to upgrade DMS table.
20:31:13,922  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerUpdateDocbaseServiceScript - The installer will update start script for  repository GR_REPO.
20:31:13,929  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerUpdateDocbaseServiceScript - The installer will update stop script for  repository GR_REPO.
20:31:13,937  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerUpgradeAEKUtility - will not execute start and stop services
20:31:13,944  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAServerUpgradeAEKUtility - will not execute start and stop services
20:31:13,947  INFO &#x5B;main]  - The installer will start component process for GR_REPO.
20:31:14,997  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
20:31:16,005  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
20:31:17,015  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
20:31:18,024  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
20:31:19,030  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
20:31:20,038  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
20:31:21,045  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
20:31:22,053  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
...
20:31:43,060  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
20:31:44,066  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
...
20:32:10,073  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
20:32:11,080  INFO &#x5B;main] com.documentum.install.server.installanywhere.actions.DiWAUnixServiceControl - logPath is $DOCUMENTUM/dba/log/GR_REPO.log
&#x5B;dmadmin@cs-0 ~]$
</pre></div>


<h2 class="wp-block-heading" id="h-3-repository-startup-crash-on-dm-server-e-sockopt">3. Repository startup crash on DM_SERVER_E_SOCKOPT?</h2>



<p class="wp-block-paragraph">Everything looked fine at the beginning of the log file, at least until the start of the Repository process. After waiting a little less than one minute, there was still nothing happening, which was not normal. Therefore, I checked the OS processes and found none for the Repository &#8220;<em><strong>GR_REPO</strong></em>&#8220;:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;dmadmin@cs-0 ~]$ ps uxf | grep GR_REPO
dmadmin  2233289  0.0  0.0   3348  1824 pts/0    S+   20:32   0:00  \_ grep --color=auto GR_REPO
&#x5B;dmadmin@cs-0 ~]$
</pre></div>


<p class="wp-block-paragraph">Therefore, something happened to the Repository process, which means that the upgrade process failed or will remain stuck in that loop. When checking the Repository log file, I saw the following:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [43,47,51,53]; title: ; notranslate">
&#x5B;dmadmin@cs-0 dba]$ cat $DOCUMENTUM/dba/log/GR_REPO.log

    OpenText Documentum Content Server (version 25.4.0000.0143 Linux64.Oracle)
    Copyright (c) 2025. OpenText Corporation
    All rights reserved.

2026-02-05T20:31:15.702051	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SERVER_I_START_SERVER]info:  &quot;Docbase GR_REPO attempting to open&quot;

2026-02-05T20:31:15.802801	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SERVER_I_START_KEY_STORAGE_MODE]info:  &quot;Docbase GR_REPO is using database for cryptographic key storage&quot;

2026-02-05T20:31:15.802878	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SERVER_I_START_SERVER]info:  &quot;Docbase GR_REPO process identity: user(dmadmin)&quot;

2026-02-05T20:31:16.073327	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Post Upgrade Processing.&quot;

2026-02-05T20:31:16.073843	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Base Types.&quot;

2026-02-05T20:31:16.074476	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize dmRecovery.&quot;

2026-02-05T20:31:16.089286	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize dmACL.&quot;

...

2026-02-05T20:31:17.886348	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Acs Config List.&quot;

2026-02-05T20:31:17.886487	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize dmLiteSysObject.&quot;

2026-02-05T20:31:17.886957	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize dmBatchManager.&quot;

2026-02-05T20:31:17.891417	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Partition Scheme.&quot;

2026-02-05T20:31:17.891883	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Critical Event Registry.&quot;

2026-02-05T20:31:17.891947	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Transaction Tracking Event Registry.&quot;

2026-02-05T20:31:17.892014	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Initialze External User Event Set.&quot;

2026-02-05T20:31:17.893580	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Authentication Plugins.&quot;

2026-02-05T20:31:17.895070	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_AUTH_PLUGIN_LOADED]info:  &quot;Loaded Authentication Plugin with code &#039;dm_krb&#039; ($DOCUMENTUM/dba/auth/libkerberos.so).&quot;

2026-02-05T20:31:17.895090	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SESSION_I_AUTH_PLUGIN_LOAD_INIT]info:  &quot;Authentication plugin ( &#039;dm_krb&#039; ) was disabled. This is expected if no keytab file(s) at location ($DOCUMENTUM/dba/auth/kerberos).Please refer the content server installation guide.&quot;

2026-02-05T20:31:17.896397	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SERVER_I_START_SERVER]info:  &quot;Docbase GR_REPO opened&quot;

2026-02-05T20:31:17.896463	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SERVER_I_SERVER]info:  &quot;Setting exception handlers to catch all interrupts&quot;

2026-02-05T20:31:17.896475	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SERVER_I_START]info:  &quot;Starting server using service name:  GR_REPO&quot;

2026-02-05T20:31:17.933282	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_LICENSE_E_NO_LICENSE_CONFIG]error:  &quot;Could not find dm_otds_license_config object.&quot;

2026-02-05T20:31:17.998180	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SERVER_I_LAUNCH_MTHDSVR]info:  &quot;Launching Method Server succeeded.&quot;

2026-02-05T20:31:17.999298	2235323&#x5B;2235323]	0000000000000000	&#x5B;DM_SERVER_E_SOCKOPT]error:  &quot;setsockopt failed for (SO_REUSEADDR (client)) with error (88)&quot;

&#x5B;dmadmin@cs-0 dba]$
</pre></div>


<h2 class="wp-block-heading" id="h-4-investigation-of-the-dm-server-e-sockopt">4. Investigation of the DM_SERVER_E_SOCKOPT</h2>



<p class="wp-block-paragraph">The Repository log file was also quite clean. Everything at the beginning was as expected, until the very last line. The Repository was about to become available when suddenly a &#8220;<em><strong>DM_SERVER_E_SOCKOPT</strong></em>&#8221; error appeared and crashed the Repository process.</p>



<p class="wp-block-paragraph">I tried to start the Repository again, but without success. It always ended up with the exact same error as the last line. It was my first time encountering that error &#8220;<em><strong>DM_SERVER_E_SOCKOPT</strong></em>&#8220;, so I checked the official documentation and the OpenText support website. But I had no luck there either; there was not a single reference to that error anywhere.</p>



<p class="wp-block-paragraph">So, what should I do next? As usual, I just tried things that could make sense based on the information available.</p>



<p class="wp-block-paragraph">The error refers to <em><strong>SOCKOPT</strong></em>, which I assumed meant socket options, so possibly something related to networking or communications. I checked the &#8220;<em><strong>setsockopt</strong></em>&#8221; documentation (c.f. <a href="https://man7.org/linux/man-pages/man3/setsockopt.3p.html" target="_blank" rel="noreferrer noopener">this man page</a>), which indeed seemed related to networking protocols. I also reviewed the &#8220;<em><strong>SO_REUSEADDR</strong></em>&#8221; documentation (c.f. <a href="https://man7.org/linux/man-pages/man7/socket.7.html" target="_blank" rel="noreferrer noopener">this other man page</a>), which refers to address binding apparently.</p>



<h2 class="wp-block-heading" id="h-5-fix-amp-resolution">5. Fix &amp; Resolution</h2>



<p class="wp-block-paragraph">With that in mind, I remembered the IPv4 versus IPv6 issue I encountered back in 2024 and the blog post I wrote about it (linked earlier). Since there was no issue starting the Connection Broker with the &#8220;<em><strong>host=${Current-IPv4}</strong></em>&#8221; setting, I focused on the Repository configuration.</p>



<p class="wp-block-paragraph">Therefore, I tried disabling what I had added for 23.4 to work. Specifically, I commented out the line &#8220;<em><strong>#ip_mode = V4ONLY</strong></em>&#8221; in &#8220;<em><strong>server.ini</strong></em>&#8221; so that it would return to the default value:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
&#x5B;dmadmin@cs-0 ~]$ grep ip_mode $DOCUMENTUM/dba/config/GR_REPO/server.ini
ip_mode = V4ONLY
&#x5B;dmadmin@cs-0 ~]$
&#x5B;dmadmin@cs-0 ~]$ sed -i &#039;s/^\(ip_mode\)/#\1/&#039; $DOCUMENTUM/dba/config/GR_REPO/server.ini
&#x5B;dmadmin@cs-0 ~]$
&#x5B;dmadmin@cs-0 ~]$ grep ip_mode $DOCUMENTUM/dba/config/GR_REPO/server.ini
#ip_mode = V4ONLY
&#x5B;dmadmin@cs-0 ~]$
</pre></div>


<p class="wp-block-paragraph">Then I tried starting the Repository again:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: java; highlight: [48,52,56,58,60]; title: ; notranslate">
&#x5B;dmadmin@cs-0 ~]$ $DOCUMENTUM/dba/dm_start_GR_REPO
starting Documentum server for repository: &#x5B;GR_REPO]
with server log: &#x5B;$DOCUMENTUM/dba/log/GR_REPO.log]
server pid: 2268601
&#x5B;dmadmin@cs-0 ~]$
&#x5B;dmadmin@cs-0 ~]$ cat $DOCUMENTUM/dba/log/GR_REPO.log

    OpenText Documentum Content Server (version 25.4.0000.0143 Linux64.Oracle)
    Copyright (c) 2025. OpenText Corporation
    All rights reserved.

2026-02-05T20:57:34.417095	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SERVER_I_START_SERVER]info:  &quot;Docbase GR_REPO attempting to open&quot;

2026-02-05T20:57:34.517792	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SERVER_I_START_KEY_STORAGE_MODE]info:  &quot;Docbase GR_REPO is using database for cryptographic key storage&quot;

2026-02-05T20:57:34.517854	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SERVER_I_START_SERVER]info:  &quot;Docbase GR_REPO process identity: user(dmadmin)&quot;

2026-02-05T20:57:34.750986	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Post Upgrade Processing.&quot;

2026-02-05T20:57:34.751390	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Base Types.&quot;

2026-02-05T20:57:34.751810	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize dmRecovery.&quot;

2026-02-05T20:57:34.763115	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize dmACL.&quot;

...

2026-02-05T20:57:35.977776	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Acs Config List.&quot;

2026-02-05T20:57:35.977873	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize dmLiteSysObject.&quot;

2026-02-05T20:57:35.978265	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize dmBatchManager.&quot;

2026-02-05T20:57:35.981015	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Partition Scheme.&quot;

2026-02-05T20:57:35.981417	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Critical Event Registry.&quot;

2026-02-05T20:57:35.981479	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Transaction Tracking Event Registry.&quot;

2026-02-05T20:57:35.981543	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Initialze External User Event Set.&quot;

2026-02-05T20:57:35.983046	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_INIT_BEGIN]info:  &quot;Initialize Authentication Plugins.&quot;

2026-02-05T20:57:35.984583	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_AUTH_PLUGIN_LOADED]info:  &quot;Loaded Authentication Plugin with code &#039;dm_krb&#039; ($DOCUMENTUM/dba/auth/libkerberos.so).&quot;

2026-02-05T20:57:35.984604	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SESSION_I_AUTH_PLUGIN_LOAD_INIT]info:  &quot;Authentication plugin ( &#039;dm_krb&#039; ) was disabled. This is expected if no keytab file(s) at location ($DOCUMENTUM/dba/auth/kerberos).Please refer the content server installation guide.&quot;

2026-02-05T20:57:35.986125	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SERVER_I_START_SERVER]info:  &quot;Docbase GR_REPO opened&quot;

2026-02-05T20:57:35.986189	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SERVER_I_SERVER]info:  &quot;Setting exception handlers to catch all interrupts&quot;

2026-02-05T20:57:35.986199	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SERVER_I_START]info:  &quot;Starting server using service name:  GR_REPO&quot;

2026-02-05T20:57:36.016709	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_LICENSE_E_NO_LICENSE_CONFIG]error:  &quot;Could not find dm_otds_license_config object.&quot;

2026-02-05T20:57:36.067861	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SERVER_I_LAUNCH_MTHDSVR]info:  &quot;Launching Method Server succeeded.&quot;

2026-02-05T20:57:36.073641	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SERVER_I_LISTENING]info:  &quot;The server is listening on network address (Service Name: GR_REPO, Host Name: cs-0 :V4 IP)&quot;

2026-02-05T20:57:36.082139	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SERVER_I_LISTENING]info:  &quot;The server is listening on network address (Service Name: GR_REPO_s, Host Name: cs-0 :V4 IP)&quot;

2026-02-05T20:57:37.135498	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_WORKFLOW_I_AGENT_START]info:  &quot;Workflow agent master (pid : 2268748, session 0101234580000007) is started sucessfully.&quot;
IsProcessAlive: Process ID 0 is not &gt; 0
2026-02-05T20:57:37.136533	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_WORKFLOW_I_AGENT_START]info:  &quot;Workflow agent worker (pid : 2268749, session 010123458000000a) is started sucessfully.&quot;
IsProcessAlive: Process ID 0 is not &gt; 0
2026-02-05T20:57:38.137780	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_WORKFLOW_I_AGENT_START]info:  &quot;Workflow agent worker (pid : 2268770, session 010123458000000b) is started sucessfully.&quot;
IsProcessAlive: Process ID 0 is not &gt; 0
2026-02-05T20:57:39.139572	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_WORKFLOW_I_AGENT_START]info:  &quot;Workflow agent worker (pid : 2268823, session 010123458000000c) is started sucessfully.&quot;
2026-02-05T20:57:40.139741	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_SERVER_I_START]info:  &quot;Sending Initial Docbroker check-point &quot;

2026-02-05T20:57:40.143121	2268601&#x5B;2268601]	0000000000000000	&#x5B;DM_MQ_I_DAEMON_START]info:  &quot;Message queue daemon (pid : 2268845, session 0101234580000456) is started sucessfully.&quot;
2026-02-05T20:57:42.758073	2268844&#x5B;2268844]	0101234580000003	&#x5B;DM_DOCBROKER_I_PROJECTING]info:  &quot;Sending information to Docbroker located on host (cs-0.cs.dctm-ns.svc.cluster.local) with port (1490).  Information: (Config(GR_REPO), Proximity(1), Status(Open), Dormancy Status(Active)).&quot;
&#x5B;dmadmin@cs-0 ~]$
</pre></div>


<p class="wp-block-paragraph">As you can see, it is working again. This means that what I previously had to add for the 23.4 environment to work is now causing an issue on 25.4. Obviously the OS is not the same (both the container and the real host), and the Kubernetes environment is also different. Still, it is interesting that something fixing an issue in a specific version can introduce a problem in a newer version.</p>



<p class="wp-block-paragraph">As shown in the logs, the Repository still starts on IPv4, as it clearly states: &#8220;<em><strong>The server is listening on network address (Service Name: GR_REPO, Host Name: cs-0 :V4 IP)</strong></em>&#8220;. However, it does not accept the setting &#8220;<em><strong>ip_mode = V4ONLY</strong></em>&#8220;, somehow.</p>



<p class="wp-block-paragraph">That&#8217;s pretty incredible, don&#8217;t you think? Anyway, once the Repository processes were running, I was able to restart the upgrade process properly by ensuring that ip_mode was not specified for version 25.4.</p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/dctm-upgrade-from-23-4-to-25-4-fails-with-dm_server_e_sockopt/">Dctm &#8211; Upgrade from 23.4 to 25.4 fails with DM_SERVER_E_SOCKOPT</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/dctm-upgrade-from-23-4-to-25-4-fails-with-dm_server_e_sockopt/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Dctm &#8211; Detect and analyze slow SQL queries</title>
		<link>https://www.dbi-services.com/blog/dctm-detect-and-analyze-slow-sql-queries/</link>
					<comments>https://www.dbi-services.com/blog/dctm-detect-and-analyze-slow-sql-queries/#respond</comments>
		
		<dc:creator><![CDATA[Morgan Patou]]></dc:creator>
		<pubDate>Wed, 25 Feb 2026 20:06:48 +0000</pubDate>
				<category><![CDATA[Enterprise content management]]></category>
		<category><![CDATA[Documentum]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[sqltrace]]></category>
		<category><![CDATA[Trace]]></category>
		<guid isPermaLink="false">https://www.dbi-services.com/blog/?p=43157</guid>

					<description><![CDATA[<p>Documentum is a powerful but also complex software. Therefore, debugging performance issues often comes down to being able to implement and review various traces. In this blog, I will talk about how you can detect and analyze slow SQL queries from a Documentum Server. Several years ago, I wrote a blog post about DFC traces. [&#8230;]</p>
<p>L’article <a href="https://www.dbi-services.com/blog/dctm-detect-and-analyze-slow-sql-queries/">Dctm &#8211; Detect and analyze slow SQL queries</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Documentum is a powerful but also complex software. Therefore, debugging performance issues often comes down to being able to implement and review various traces. In this blog, I will talk about how you can detect and analyze slow SQL queries from a Documentum Server. Several years ago, I wrote a <a href="https://www.dbi-services.com/blog/documentum-dfc-traces-setup-investigation/" id="10660" target="_blank" rel="noreferrer noopener">blog post about DFC traces</a>. It included the setup and investigation process, using the EMC Python/AWK parser scripts as a base. In addition, I also wrote <a href="https://www.dbi-services.com/blog/howto-detect-and-analyze-long-stuck-threads-in-tomcat/" id="29794" target="_blank" rel="noreferrer noopener">another one about Tomcat slow/stuck threads</a>. With these two posts, you could already cover the Documentum DFC clients (like JMS Apps, DA, D2, xPlore, etc.).</p>



<p class="wp-block-paragraph">As you might imagine, SQL traces in Documentum can be quite large. A lot of queries are sent to the Database to retrieve, insert or update information. Things like document metadata, users, permissions, audittrail entries, document creation, etc. It is less intensive than DFC or SSL/Network traces, because those write dozens of lines for each and every action. On the other hand, SQL traces limit themselves to the query being executed, the execution time, and potentially some commits.</p>



<p class="wp-block-paragraph">A customer recently provided me a Repository log file with SQL traces related to a problematic action in DA. I was expecting maybe a 10 or 20-minute log file at most. But what I received was a 4-hour log file, because the action took that long to complete in DA. I thought this would be a good use case to share in a blog, to explain how you can quickly extract interesting details from such SQL traces.</p>



<h2 class="wp-block-heading" id="h-1-defining-variables">1. Defining variables</h2>



<p class="wp-block-paragraph">As often, I find it easier for both you and me to use predefined variables. This allows you to just change these details and, for the rest of the blog, simply copy/paste the exact same queries without having to wonder whether they will work.</p>



<p class="wp-block-paragraph">I&#8217;m using my Mac and I have a folder which contains the Repository log file:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
mac:~$ ls
dbi01.log
mac:~$
mac:~$ # Variables definition
mac:~$ source_repo_log_file=&quot;dbi01.log&quot;
mac:~$ query_times=&quot;query_times.log&quot;
mac:~$ extracted_session_log_file=&quot;extracted_session_log_file.log&quot;
</pre></div>


<h2 class="wp-block-heading" id="h-2-extracting-the-sql-execution-times">2. Extracting the SQL execution times</h2>



<p class="wp-block-paragraph">Once done, let&#8217;s proceed directly with extracting the SQL execution times. The durations will be sorted to easily understand whether there is a problem with some queries. In the initial log file, there are more than a hundred thousand SQL queries executed. Out of these, I selected only the 47 slowest &#8220;<em><strong>EXEC</strong></em>&#8221; entries below. That is not an arbitrary number; I initially retrieved the last 100 lines, but most of them were less than 0.1s. Therefore, I reduced that number to only execution times that might be interesting to investigate.</p>



<p class="wp-block-paragraph">In the first command, I am selecting all &#8220;<strong><em>EXEC</em></strong>&#8220;, which will include the base &#8220;<strong><em>EXEC</em></strong>&#8221; but also other types like &#8220;<strong><em>EXECBATCH</em></strong>&#8220;, if any. There are also some &#8220;<strong><em>FETCH</em></strong>&#8221; entries in the SQL traces, but I will ignore these in this blog. The second and third commands simply show what was extracted:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
mac:~$ ### Extracting all &quot;EXEC&quot; times from the Repo log
mac:~$ ### keeping the 47 slowest (&gt;0.5s in this case)
mac:~$ grep &quot;EXEC&quot; ${source_repo_log_file} | \
         awk &#039;{print $7}&#039; | \
         sort --version-sort | \
         tail -47 &gt; ${query_times}
mac:~$
mac:~$ wc -l ${query_times}
    47 query_times.log
mac:~$
mac:~$
mac:~$ ### Displaying the outcome sorted by asc time
mac:~$ cat ${query_times}
0.5566090000
1.0761300000
9.9041350000
11.2433810000
14.6858060000
...
786.4434090000
961.8524350000
1028.2339260000
1500.6862730000
1970.6185150000
mac:~$
</pre></div>


<p class="wp-block-paragraph">The above command can be useful if you only want to extract the execution time (7th column). But most of the time, what is useful is the full line. It will display the <strong><em>date</em></strong> (1st column), of course as well as the <em><strong>OS PID</strong></em> (2nd column) that is executing this query, which can be useful in some cases. But most importantly, it will display the <em><strong>Session ID</strong></em> (3rd column) linked to the request. For that purpose, you can slightly modify the first command from above, to extract the full line for all &#8220;<em><strong>EXEC</strong></em>&#8221; and sort based on the 7th column:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
mac:~$ ### Extracting all &quot;EXEC&quot; complete lines from the Repo log
mac:~$ ### keeping the 47 slowest (&gt;0.5s in this case)
mac:~$ grep &quot;EXEC&quot; ${source_repo_log_file} | \
         sort -k 7 --version-sort | \
         tail -47
2025-12-19T12:25:33.056 102&#x5B;102] 0112345682105da7 &#x5B;SQL] 0 EXEC 0.5566090000
2025-12-19T11:09:40.772 104&#x5B;104] 0112345682105d65 &#x5B;SQL] 0 EXEC 1.0761300000
2025-12-19T12:01:28.042 110&#x5B;110] 0112345682105d9b &#x5B;SQL] 0 EXEC 9.9041350000
2025-12-19T12:01:13.943 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 11.2433810000
2025-12-19T11:59:16.078 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 14.6858060000
...
2025-12-19T11:03:02.001 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 786.4434090000
2025-12-19T09:59:20.090 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 961.8524350000
2025-12-19T11:59:01.360 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 1028.2339260000
2025-12-19T10:49:55.553 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 1500.6862730000
2025-12-19T09:29:55.637 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 1970.6185150000
mac:~$
</pre></div>


<p class="wp-block-paragraph">Another way to do (almost) the same thing is based on the previously exported execution times. If you loop over them with a certain command, you can retrieve the same lines again. The only difference is that this second method will be significantly slower (since it parses the log file for each execution time instead of only once as in the previous command) and slightly less accurate in some edge cases. Technically, if you have two lines with the exact same execution time, you would not get only 2 results, but 2 x 2 results (so there would be duplicates). In my case, I do not have duplicate execution times, so it produces 100% the same outcome:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
mac:~$ ### Extracting all &quot;EXEC&quot; complete lines from the Repo log
mac:~$ ### based on previously extracted execution time
mac:~$ while read line; do \
         grep &quot;EXEC.*&#x5B;&#x5B;:space:]]\+${line}$&quot; ${source_repo_log_file}; \
       done &lt; &lt;(cat ${query_times})
2025-12-19T12:25:33.056 102&#x5B;102] 0112345682105da7 &#x5B;SQL] 0 EXEC 0.5566090000
2025-12-19T11:09:40.772 104&#x5B;104] 0112345682105d65 &#x5B;SQL] 0 EXEC 1.0761300000
2025-12-19T12:01:28.042 110&#x5B;110] 0112345682105d9b &#x5B;SQL] 0 EXEC 9.9041350000
2025-12-19T12:01:13.943 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 11.2433810000
2025-12-19T11:59:16.078 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 14.6858060000
...
2025-12-19T11:03:02.001 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 786.4434090000
2025-12-19T09:59:20.090 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 961.8524350000
2025-12-19T11:59:01.360 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 1028.2339260000
2025-12-19T10:49:55.553 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 1500.6862730000
2025-12-19T09:29:55.637 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 1970.6185150000
mac:~$
</pre></div>


<h2 class="wp-block-heading" id="h-3-how-long-is-really-spent-on-queries">3. How long is really spent on queries?</h2>



<p class="wp-block-paragraph">Based on the above results, the first thing that stood out was that <em><strong>44</strong></em> of the slowest queries appeared to be linked to the <em><strong>same Session ID</strong></em>. Therefore, except for the first 3 rows (0.5s, 1s, and 9.9s), all other queries came from a single source. This clearly indicates that the problematic action in DA is associated with that Session ID, and it is therefore what I need to investigate further. The next step was to extract all logs related to that Session ID:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
mac:~$ ### Extracting logs related to the specific session ID
mac:~$ session_id=&quot;01123456821058fb&quot;
mac:~$ grep &quot;${session_id}&quot; ${source_repo_log_file} \
         &gt; ${extracted_session_log_file}
mac:~$
mac:~$ wc -l ${extracted_session_log_file}
    765 extracted_session_log_file.log
mac:~$
</pre></div>


<p class="wp-block-paragraph">Compared to the size of the log file (a few hundred thousand lines), there are only 765 lines related to that Session ID. But how much time is actually spent on these few requests? For that purpose, you can compare the sum of all &#8220;<em><strong>EXEC</strong></em>&#8221; <em><strong>times for the Repository log file</strong></em> vs <em><strong>the Session ID log file</strong></em> vs <em><strong>the 44 slowest queries</strong></em>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [12,26,40]; title: ; notranslate">
mac:~$ ### Checking the SQL execution time
mac:~$ ### for all SQL queries in the Repo log file
mac:~$ grep &quot;EXEC&quot; ${source_repo_log_file} | \
         awk &#039;{print $7}&#039; | \
         awk &#039;{sum += $1}
         END {
           h = int(sum / 3600)
           m = int((sum % 3600) / 60)
           s = sum % 60
           printf &quot;%02dh %02dm %.3fs\n&quot;, h, m, s
         }&#039;
03h 11m 50.477s
mac:~$
mac:~$
mac:~$ ### Checking the SQL execution time
mac:~$ ### related to the specific session ID
mac:~$ grep &quot;EXEC&quot; ${extracted_session_log_file} | \
         awk &#039;{print $7}&#039; | \
         awk &#039;{sum += $1}
         END {
           h = int(sum / 3600)
           m = int((sum % 3600) / 60)
           s = sum % 60
           printf &quot;%02dh %02dm %.3fs\n&quot;, h, m, s
         }&#039;
03h 09m 56.340s
mac:~$
mac:~$
mac:~$ ### Checking the SQL execution time
mac:~$ ### for the 44 slowest related to the specific session ID
mac:~$ cat ${query_times} | \
         tail -44 | \
         awk &#039;{sum += $1}
         END {
           h = int(sum / 3600)
           m = int((sum % 3600) / 60)
           s = sum % 60
           printf &quot;%02dh %02dm %.3fs\n&quot;, h, m, s
         }&#039;
03h 09m 56.015s
mac:~$
</pre></div>


<p class="wp-block-paragraph">The above means that inside that ~4-hour log file, the Documentum Server executed more than a hundred thousand queries for a total sum of 3h 11m 50.5s. Of course, that is not necessarily consecutive time, as some queries might be executed in parallel by different processes. Out of these 3h 11m 50s spent on Database queries, the time related to the problematic Session ID, which represents slightly less than 0.2% of the executed SQL queries, is 3h 9m 56.3s. And out of these 0.2%, the 44 slowest queries represent 3h 9m 56.0s. That means:</p>



<ul class="wp-block-list">
<li>100% SQL queries &gt;&gt; 3h 11m 50.5s = 11&#8217;510.5s
<ul class="wp-block-list">
<li><em><strong>99.8% SQL queries</strong></em> &gt;&gt; 1m 54s = <em><strong>114s</strong></em></li>



<li>0.2% SQL queries &gt;&gt; 3h 9m 56.3s = 11&#8217;396.3s
<ul class="wp-block-list">
<li><em><strong>0.165% SQL queries</strong></em> &gt;&gt; <em><strong>0.3s</strong></em></li>



<li><em><strong>0.035% SQL queries</strong></em> &gt;&gt; 3h 9m 56.0s = <em><strong>11&#8217;396.0s</strong></em></li>
</ul>
</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">In other words, <em><strong>0.035% of the SQL queries executed represent 99% of the time spent on the Database</strong></em>.</p>



<h2 class="wp-block-heading" id="h-4-finding-the-responsible-queries">4. Finding the responsible queries</h2>



<p class="wp-block-paragraph">Now that we have clear evidence related to the execution times, the next step is to retrieve the associated queries that are the source of the performance issue. Below, I am simply retrieving the line containing the execution time, as well as the previous line, which is supposed to contain the triggered query.</p>



<p class="wp-block-paragraph">This is normally safe because I am checking only the extracted Session ID logs. Since a session is most likely sequential, it is relatively straightforward to retrieve the query responsible for the long execution times. There might be parallel executions for some batch processing, but I am not sure whether that would use the same session ID or a dedicated one per batch/thread.</p>



<p class="wp-block-paragraph">If you have long running SQLs for multiple sessions, then you might want/need to take care of that and do the check session by session.</p>



<p class="wp-block-paragraph">If you try to use the full Repository log instead, you will probably end up with an incorrect N-1 line. In that case, you would need a more complex parsing mechanism based on the Session ID. You can check the blog I mentioned earlier for an example of DFC trace parsing using awk. For this blog, I am keeping it simple, but I might create another post with a more advanced parser that performs everything automatically. So, let&#8217;s extract the SQL queries related to the 44 slowest execution times:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
mac:~$ ### Extracting all 44 slowest queries
mac:~$ ### from the specific session ID log file
mac:~$ while read line; do \
         grep -B1 &quot;${line}&quot; ${extracted_session_log_file}; \
       done &lt; &lt;(cat ${query_times})
2025-12-19T12:01:02.700 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dm_audittrail.session_id, dm_audittrail.user_name, dm_audittrail.event_name, dm_audittrail.event_description, dm_audittrail.time_stamp, dm_audittrail.object_type from dm_audittrail_sp  dm_audittrail where (dm_audittrail.event_description like &#039;&#x5B;TRANSACTION_TRACKING_EVENTS]%&#039;)
2025-12-19T12:01:13.943 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 11.2433810000
2025-12-19T11:59:01.393 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dm_audittrail.user_name, dm_audittrail.session_id, dm_audittrail.event_name, dm_audittrail.string_1, dm_audittrail.string_2, dm_audittrail.string_3, dm_audittrail.string_4, dm_audittrail.time_stamp from dm_audittrail_sp  dm_audittrail where (dm_audittrail.event_name in (&#039;dm_connect&#039;, &#039;dm_logon_failure&#039;))
2025-12-19T11:59:16.078 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 14.6858060000
2025-12-19T12:00:09.566 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dm_audittrail.session_id, dm_audittrail.user_name, dm_audittrail.time_stamp from dm_audittrail_sp  dm_audittrail where ((dm_audittrail.event_name=&#039;dm_connect&#039;) and dm_audittrail.user_name in (select all dm_repeating.users_names from dm_group_sp  dm_group, dm_group_rp dm_repeating where ((dm_group.group_name=&#039;dm_occasional_user_role&#039;)) and dm_repeating.r_object_id=dm_group.r_object_id )) order by dm_audittrail.r_object_id desc
2025-12-19T12:01:02.696 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 53.1301340000
2025-12-19T11:59:16.083 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dm_audittrail.session_id, dm_audittrail.user_name, dm_audittrail.time_stamp from dm_audittrail_sp  dm_audittrail where ((dm_audittrail.event_name=&#039;dm_disconnect&#039;) and dm_audittrail.user_name in (select all dm_repeating.users_names from dm_group_sp  dm_group, dm_group_rp dm_repeating where ((dm_group.group_name=&#039;dm_occasional_user_role&#039;)) and dm_repeating.r_object_id=dm_group.r_object_id )) order by dm_audittrail.r_object_id desc
2025-12-19T12:00:09.563 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 53.4798570000
2025-12-19T11:13:44.269 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000153&#039;)) ))
2025-12-19T11:15:01.033 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 76.7641310000
2025-12-19T11:18:59.434 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000154&#039;)) ))
2025-12-19T11:20:16.256 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 76.8221030000
2025-12-19T11:08:31.916 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000152&#039;)) ))
2025-12-19T11:09:49.809 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 77.8924590000
2025-12-19T10:10:44.752 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;281234568000010b&#039;)) ))
2025-12-19T10:12:03.103 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 78.3507420000
2025-12-19T11:03:02.009 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000151&#039;)) ))
2025-12-19T11:04:20.605 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 78.5954180000
2025-12-19T11:36:05.711 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;28123456800001b4&#039;)) ))
2025-12-19T11:37:27.600 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 81.8885210000
2025-12-19T11:24:26.958 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000155&#039;)) ))
2025-12-19T11:25:49.718 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 82.7602900000
2025-12-19T11:30:19.107 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;28123456800001aa&#039;)) ))
2025-12-19T11:31:44.148 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 85.0403360000
2025-12-19T09:32:33.679 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000102&#039;)) ))
2025-12-19T09:34:06.477 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 92.7986000000
2025-12-19T10:03:31.061 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;281234568000010a&#039;)) ))
2025-12-19T10:05:07.569 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 96.5074910000
2025-12-19T11:11:47.250 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000152&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T11:13:44.264 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 117.0139310000
2025-12-19T11:09:49.815 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000152&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T11:11:47.247 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 117.4327020000
2025-12-19T11:17:00.411 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000153&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T11:18:59.431 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 119.0201020000
2025-12-19T11:15:01.040 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000153&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T11:17:00.406 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 119.3658150000
2025-12-19T11:04:20.613 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000151&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T11:06:23.110 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 122.4972100000
2025-12-19T11:22:23.775 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000154&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T11:24:26.953 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 123.1771960000
2025-12-19T10:12:03.114 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;281234568000010b&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T10:14:09.285 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 126.1710020000
2025-12-19T10:14:09.290 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;281234568000010b&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T10:16:15.546 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 126.2559010000
2025-12-19T11:20:16.262 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000154&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T11:22:23.770 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 127.5082070000
2025-12-19T11:33:56.973 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;28123456800001aa&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T11:36:05.705 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 128.7315320000
2025-12-19T11:06:23.115 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000151&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T11:08:31.910 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 128.7948250000
2025-12-19T11:37:27.606 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;28123456800001b4&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T11:39:39.265 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 131.6585850000
2025-12-19T11:31:44.156 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;28123456800001aa&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T11:33:56.971 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 132.8144410000
2025-12-19T11:39:39.270 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;28123456800001b4&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T11:41:53.017 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 133.7473340000
2025-12-19T11:25:49.725 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000155&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T11:28:03.796 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 134.0701630000
2025-12-19T11:28:03.801 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000155&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T11:30:19.104 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 135.3037450000
2025-12-19T09:29:55.642 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000100&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T09:32:33.673 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 158.0316130000
2025-12-19T09:34:06.482 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000102&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T09:36:51.885 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 165.4027790000
2025-12-19T10:07:57.988 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;281234568000010a&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T10:10:44.749 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 166.7611230000
2025-12-19T10:05:07.576 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;281234568000010a&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T10:07:57.984 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 170.4081960000
2025-12-19T09:36:51.889 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000102&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T09:39:43.525 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 171.6363780000
2025-12-19T09:39:43.530 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;6d1234568000011e&#039;)) ))
2025-12-19T09:43:18.230 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 214.6996070000
2025-12-19T09:59:20.094 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;total_file_count&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;6d1234568000011e&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T10:03:31.053 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 250.9586420000
2025-12-19T08:51:17.559 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000100&#039;)) ))
2025-12-19T08:57:05.010 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 347.4517930000
2025-12-19T10:16:15.551 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all dmr_content.set_time from dmr_content_sp  dmr_content where (dmr_content.set_time=(select all max(dmr_content.set_time) from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000150&#039;)) ))
2025-12-19T10:24:54.860 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 519.3084910000
2025-12-19T10:49:55.557 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000150&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is null) and (dmr_content.is_archived=0))
2025-12-19T11:03:02.001 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 786.4434090000
2025-12-19T09:43:18.238 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;total_file_count&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;6d1234568000011e&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T09:59:20.090 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 961.8524350000
2025-12-19T11:41:53.126 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all u.r_object_id, u.user_name, u.user_privileges, u.user_xprivileges, count(s.object_name) &quot;document_count&quot; from dm_user_sp  u, dm_sysobject_sp  s where ((u.user_name=s.owner_name)) and (s.i_has_folder = 1 and s.i_is_deleted = 0) group by u.user_name, u.user_privileges, u.user_xprivileges, u.r_object_id
2025-12-19T11:59:01.360 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 1028.2339260000
2025-12-19T10:24:54.866 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000150&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T10:49:55.553 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 1500.6862730000
2025-12-19T08:57:05.019 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 select all count(*) &quot;totalsize&quot; from dmr_content_sp  dmr_content where ((dmr_content.storage_id=&#039;2812345680000100&#039;) and  exists (select r_object_id from dmr_content_r where dmr_content.r_object_id = r_object_id and parent_id is not null) and (dmr_content.is_archived=0))
2025-12-19T09:29:55.637 105&#x5B;105] 01123456821058fb &#x5B;SQL] 0 EXEC 1970.6185150000
mac:~$
</pre></div>


<p class="wp-block-paragraph">There are quite a few interesting things we can see above. The <strong><em>slowest queries (&gt;76s) are all related to documents/contents</em></strong>:</p>



<ul class="wp-block-list">
<li>For each dm_filestore that exists in the Repository (12 above):
<ul class="wp-block-list">
<li>1 x &#8216;<em><strong>select all dmr_content.set_time from dmr_content_sp …</strong></em>&#8216;</li>



<li>2 x &#8216;<strong><em>select all count(*) &#8220;totalsize&#8221; from dmr_content_sp …</em></strong>&#8216; (objects with and without parent_id)</li>
</ul>
</li>



<li>For each dm_ca_store that exists in the Repository (1 above):
<ul class="wp-block-list">
<li>1 x &#8216;<em><strong>select all dmr_content.set_time from dmr_content_sp …</strong></em>&#8216;</li>



<li>2 x &#8216;<em><strong>select all count(*) &#8220;total_file_count&#8221; from dmr_content_sp …</strong></em>&#8216; (objects with and without parent_id)</li>
</ul>
</li>



<li>1 x &#8216;<em><strong>select all u.r_object_id, u.user_name, u.user_privileges, u.user_xprivileges, count(s.object_name) &#8220;document_count&#8221; from dm_user_sp u, dm_sysobject_sp s …</strong></em>&#8216;</li>
</ul>



<p class="wp-block-paragraph">This shows that the 40 slowest queries are:</p>



<ul class="wp-block-list">
<li>retrieving the set_time for all documents in a storage</li>



<li>counting the number of documents in a storage</li>



<li>retrieving all users and their associated documents</li>
</ul>



<p class="wp-block-paragraph">The <em><strong>slightly faster (but still slow) queries (11s &lt; x &lt; 53s)</strong></em> are linked to the <em><strong>audittrail</strong></em>, retrieving entries related to &#8216;<em><strong>[TRANSACTION_TRACKING_EVENTS]</strong></em>&#8216;, &#8216;<em><strong>dm_connect</strong></em>&#8216;, &#8216;<em><strong>dm_logon_failure</strong></em>&#8216;, or &#8216;<em><strong>dm_disconnect</strong></em>&#8216; events, as well as the &#8216;<em><strong>dm_occasional_user_role</strong></em>&#8216; group.</p>



<h2 class="wp-block-heading" id="h-5-linking-queries-to-the-initial-issue">5. Linking queries to the initial issue</h2>



<p class="wp-block-paragraph">The final step, once you know all the problematic/slowest requests, is either to try to improve the queries or at least find a way to avoid them. This can take various forms, such as changing the query (if it comes from custom development/configuration), improving the execution plan at the DB level, or adding indexes.</p>



<p class="wp-block-paragraph">In this case, as a reminder, the issue was a problematic action in DA. A user was trying to execute the &#8220;<em><strong>External Transaction Activity</strong></em>&#8221; report. If I am not mistaken, this is a relatively new System Report available since version 23.4 and backported to earlier versions through a HotFix. However, even in 23.4, it also requires a hotfix to work properly, as the OOTB version is unusable because of some logging errors.</p>



<p class="wp-block-paragraph">This report is supposed to fetch and display:</p>



<ul class="wp-block-list">
<li>the list of users considered &#8220;<em><strong>external</strong></em>&#8221; (e.g. part of <em><strong>dm_external_users</strong></em> or <em><strong>dm_extuser_data_access</strong></em> groups)</li>



<li>the number of transactions performed by these external users for the selected year and split by quarter</li>



<li>the total number of transactions for all external users</li>
</ul>



<p class="wp-block-paragraph">Nothing in this report should require document storage queries to be executed. Even worse, these long-running SQL queries are actually <em><strong>triggered twice</strong></em>. When you access the &#8220;<em><strong>External Transaction Activity</strong></em>&#8221; report page in DA, it immediately triggers all the report queries in the background, even if you have not selected a year or clicked any button. Then, when you select a year and click &#8220;<em><strong>Generate</strong></em>&#8221; or &#8220;<em><strong>Export</strong></em>&#8220;, it <em><strong>re-triggers the exact same queries a second time</strong></em>.</p>



<p class="wp-block-paragraph">In the Repository traces discussed in this blog, it was only the initial access to the form page &#8211; and it already took 4 hours. That customer would therefore likely need around 8 hours to generate and display the report. And this was not even their PROD environment, but a smaller DEV one.</p>



<p class="wp-block-paragraph">That is obviously a bug (and arguably a design issue). I am currently in discussion with <a href="https://www.opentext.com/" target="_blank" rel="noreferrer noopener">OpenText</a> to find a way to fix the problem in version 23.4. Please note that in version 25.4, several improvements were made to the System Report, and it appears to have fixed this specific issue from 25.4 onwards.</p>



<p class="wp-block-paragraph">This blog and trace file were just an example, but one that really happened at a customer site. I hope it provided some useful insights and that it will help you in your investigations!</p>



<p class="wp-block-paragraph"></p>
<p>L’article <a href="https://www.dbi-services.com/blog/dctm-detect-and-analyze-slow-sql-queries/">Dctm &#8211; Detect and analyze slow SQL queries</a> est apparu en premier sur <a href="https://www.dbi-services.com/blog">dbi Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.dbi-services.com/blog/dctm-detect-and-analyze-slow-sql-queries/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 
Lazy Loading (feed)

Served from: www.dbi-services.com @ 2026-06-15 17:37:22 by W3 Total Cache
-->