feat: first announcement
publish / Build-and-publish (push) Successful in 12m44s

This commit is contained in:
4gl
2026-07-26 15:27:48 +01:00
parent 8a10d127c1
commit 73fccc70a5
21 changed files with 571 additions and 480 deletions
+3
View File
@@ -1 +1,4 @@
legacy-peer-deps=true legacy-peer-deps=true
ignore-scripts=true
save-exact=true
fund=false
+5 -1
View File
@@ -6,4 +6,8 @@ Issues live as markdown files under `.scratch/`. See `docs/agents/issue-tracker.
### Domain docs ### Domain docs
Single-context layout `CONTEXT.md` + `docs/adr/` at the repo root. See `docs/agents/domain.md`. Single-context layout - `CONTEXT.md` + `docs/adr/` at the repo root. See `docs/agents/domain.md`.
### Writing style
Use regular dashes (`-`) in content, not em-dashes (`—`).
@@ -0,0 +1,25 @@
---
title: 'Complex Excel Uploads with Excel Maps'
description: Extract scattered, dynamic data from any Excel workbook using configurable Excel Maps - no manual reformatting required.
date: '2026-07-26 09:00:00'
author: 'Data Controller'
authorLink: https://www.linkedin.com/showcase/data_controller
tags:
- Announcements
---
# Complex Excel Uploads with Excel Maps
Not all spreadsheets arrive in a tidy, tabular format - and now they don't have to. Data Controller's **Complex Excel Upload** feature lets you extract data from anywhere in a workbook using an **Excel Map**: a configurable set of rules for loading specific cells and ranges. Each rule defines a range ID, a start, and an end, and can use absolute or relative references - for example, matching a specific string in a cell, then moving down and across to capture the desired value. This makes it ideal for business reports and financial spreadsheets where the layout is dynamic and the data you need is scattered across multiple locations.
Once you've dragged in the spreadsheet and the rules have run, submitting the data stages it alongside a copy of the original Excel file - preserving a complete audit trail right back to the source. The extracted data is transposed into a single column in the [`MPE_XLMAP_DATA`](https://docs.datacontroller.io/tables/mpe_xlmap_data/) table, ready for you to build jobs that present it however your business requires. You can also adjust your map's attributes in the [`MPE_XLMAP_INFO`](https://docs.datacontroller.io/tables/mpe_xlmap_info/) table to load data into different target tables.
All of this flows through the standard Data Controller approval process and data quality checks - governance included, straight out of the box.
See it in action:
<div style="position: relative; padding-top: 56.25%; margin-bottom: 2rem;"><iframe title="Complex Excel Uploads" width="100%" height="100%" src="https://vid.4gl.io/videos/embed/7jRSxNs2ENJ3dBEZun4cPf?peertubeLink=0" style="border: 0px; position: absolute; inset: 0px;" allow="fullscreen" sandbox="allow-same-origin allow-scripts allow-popups allow-forms"></iframe></div>
![Example Excel Map configuration](xlmap_example.png)
Learn more in the [documentation](https://docs.datacontroller.io/excel/#complex-excel-uploads).
Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

-1
View File
@@ -15,7 +15,6 @@ module.exports = {
plugins: [ plugins: [
'gatsby-plugin-styled-components', 'gatsby-plugin-styled-components',
'gatsby-plugin-image', 'gatsby-plugin-image',
'gatsby-plugin-react-helmet',
'gatsby-plugin-sitemap', 'gatsby-plugin-sitemap',
{ {
resolve: 'gatsby-plugin-manifest', resolve: 'gatsby-plugin-manifest',
+439 -395
View File
File diff suppressed because it is too large Load Diff
+1 -9
View File
@@ -30,7 +30,6 @@
"gatsby-plugin-local-search": "^2.0.1", "gatsby-plugin-local-search": "^2.0.1",
"gatsby-plugin-manifest": "^5.15.0", "gatsby-plugin-manifest": "^5.15.0",
"gatsby-plugin-matomo": "^0.17.0", "gatsby-plugin-matomo": "^0.17.0",
"gatsby-plugin-react-helmet": "^6.15.0",
"gatsby-plugin-sharp": "^5.15.0", "gatsby-plugin-sharp": "^5.15.0",
"gatsby-plugin-sitemap": "^6.15.0", "gatsby-plugin-sitemap": "^6.15.0",
"gatsby-plugin-styled-components": "^6.15.0", "gatsby-plugin-styled-components": "^6.15.0",
@@ -43,7 +42,6 @@
"nostr-tools": "^2.7.0", "nostr-tools": "^2.7.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-helmet": "^6.1.0",
"react-icons": "^5.2.1", "react-icons": "^5.2.1",
"react-share": "^5.1.0", "react-share": "^5.1.0",
"react-use-flexsearch": "^0.1.1", "react-use-flexsearch": "^0.1.1",
@@ -56,19 +54,13 @@
"@types/node": "^20.19.43", "@types/node": "^20.19.43",
"@types/react": "^18.3.31", "@types/react": "^18.3.31",
"@types/react-dom": "^18.3.7", "@types/react-dom": "^18.3.7",
"@types/react-helmet": "^6.1.11",
"bootstrap": "^5.0.0-beta3", "bootstrap": "^5.0.0-beta3",
"cloudron-surfer": "^6.4.1", "cloudron-surfer": "^6.4.1",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"typescript": "^7.0.2" "typescript": "^7.0.2"
}, },
"allowScripts": {
"lmdb@2.5.2": true,
"lmdb@2.5.3": true,
"msgpackr@1.10.2": true,
"@parcel/watcher@2.5.1": true
},
"overrides": { "overrides": {
"sharp": "^0.33.5",
"msgpackr": "^1.12.1", "msgpackr": "^1.12.1",
"lmdb": "^2.9.4", "lmdb": "^2.9.4",
"ordered-binary": "^1.6.1", "ordered-binary": "^1.6.1",
-5
View File
@@ -1,15 +1,10 @@
import styled from 'styled-components' import styled from 'styled-components'
import background from '../../images/home_hero_bg.png'
export const Hero = styled.main<{ bg?: boolean }>` export const Hero = styled.main<{ bg?: boolean }>`
position: relative; position: relative;
padding: 50px 0; padding: 50px 0;
color: white; color: white;
background-color: #314351; background-color: #314351;
background-repeat: no-repeat;
background-image: ${(props) => (props.bg ? `url(${background})` : 'none')};
background-attachment: scroll;
background-position: bottom right;
` `
export const HeroHeading = styled.h1` export const HeroHeading = styled.h1`
+1 -1
View File
@@ -23,7 +23,7 @@ const naviLinks: NaviLink[] = [
{ name: 'Feed', url: '/feed/', active: 'no' }, { name: 'Feed', url: '/feed/', active: 'no' },
{ name: 'FAQ', url: '/faq/', active: 'no' }, { name: 'FAQ', url: '/faq/', active: 'no' },
{ {
name: 'Documentation', name: 'Docs',
url: 'https://docs.datacontroller.io/', url: 'https://docs.datacontroller.io/',
active: 'no' active: 'no'
}, },
+21 -17
View File
@@ -1,5 +1,4 @@
import * as React from 'react' import * as React from 'react'
import { Helmet } from 'react-helmet'
import { useStaticQuery, graphql } from 'gatsby' import { useStaticQuery, graphql } from 'gatsby'
import type { SeoProps } from '../types' import type { SeoProps } from '../types'
@@ -38,21 +37,7 @@ const Seo: React.FC<SeoProps> = ({
? `${siteUrl}${previewImg}` ? `${siteUrl}${previewImg}`
: `${siteUrl}/img/data-controller.svg` : `${siteUrl}/img/data-controller.svg`
return ( const metaTags = (
<Helmet
htmlAttributes={{
lang
}}
title={pageTitle}
link={[
{
rel: 'alternate',
type: 'application/rss+xml',
title: `${defaultTitle} RSS Feed`,
href: `${siteUrl}rss.xml`
}
]}
meta={(
[ [
{ name: 'author', property: 'author', content: author }, { name: 'author', property: 'author', content: author },
{ {
@@ -71,8 +56,27 @@ const Seo: React.FC<SeoProps> = ({
{ property: `og:title`, content: title }, { property: `og:title`, content: title },
{ property: `og:type`, content: `website` } { property: `og:type`, content: `website` }
] as Array<{ name?: string; property?: string; content?: string }> ] as Array<{ name?: string; property?: string; content?: string }>
).concat(meta)} ).concat(meta)
return (
<>
<html lang={lang} />
<title>{pageTitle}</title>
<link
rel="alternate"
type="application/rss+xml"
title={`${defaultTitle} RSS Feed`}
href={`${siteUrl}rss.xml`}
/> />
{metaTags.map((tag, index) => (
<meta
key={tag.name || tag.property || index}
name={tag.name}
property={tag.property}
content={tag.content}
/>
))}
</>
) )
} }
+2 -1
View File
@@ -14,7 +14,6 @@ const NotFound: React.FC<PageProps<unknown, PageContext>> = ({
pageContext pageContext
}) => ( }) => (
<Layout location={location} heroSection={false}> <Layout location={location} heroSection={false}>
<Seo title="Page Not Found" />
<Section color="black" bgColor="white" bottomArrow={false}> <Section color="black" bgColor="white" bottomArrow={false}>
<SideBar <SideBar
pageContext={pageContext} pageContext={pageContext}
@@ -26,3 +25,5 @@ const NotFound: React.FC<PageProps<unknown, PageContext>> = ({
) )
export default NotFound export default NotFound
export const Head = () => <Seo title="Page Not Found" />
+2 -1
View File
@@ -17,7 +17,6 @@ const About: React.FC<PageProps<unknown>> = ({ location }) => {
heading="Behind Data Controller" heading="Behind Data Controller"
desc="Data Controller is a product of 4GL, a brand of Bowe IO Ltd" desc="Data Controller is a product of 4GL, a brand of Bowe IO Ltd"
> >
<Seo title="About" />
<Section color="black" bgColor="white"> <Section color="black" bgColor="white">
<div className="row"> <div className="row">
<div className="offset-md-2 col-md-8"> <div className="offset-md-2 col-md-8">
@@ -46,3 +45,5 @@ const About: React.FC<PageProps<unknown>> = ({ location }) => {
} }
export default About export default About
export const Head = () => <Seo title="About" />
+2 -1
View File
@@ -58,7 +58,6 @@ const Contact: React.FC<PageProps<unknown>> = ({ location }) => {
heading="Book a Demo" heading="Book a Demo"
desc="Schedule a demonstration of the Data Controller to see our latest features and explore use cases" desc="Schedule a demonstration of the Data Controller to see our latest features and explore use cases"
> >
<Seo title="Contact" />
<Section color="black" bgColor="white" bottomArrow={false}> <Section color="black" bgColor="white" bottomArrow={false}>
<div className="row"> <div className="row">
<div className="col-md-6"> <div className="col-md-6">
@@ -193,3 +192,5 @@ Message: ${message}`
} }
export default Contact export default Contact
export const Head = () => <Seo title="Contact" />
+2 -1
View File
@@ -20,7 +20,6 @@ const Faq: React.FC<PageProps<unknown>> = ({ location }) => {
heading="Data Controller FAQ" heading="Data Controller FAQ"
desc="Most common user questions and answers regarding the Data Controller" desc="Most common user questions and answers regarding the Data Controller"
> >
<Seo title="FAQ" />
<Section color="black" bgColor="white"> <Section color="black" bgColor="white">
<div className="row"> <div className="row">
<div className="col-md-8"> <div className="col-md-8">
@@ -67,3 +66,5 @@ const Faq: React.FC<PageProps<unknown>> = ({ location }) => {
} }
export default Faq export default Faq
export const Head = () => <Seo title="FAQ" />
+2 -1
View File
@@ -67,7 +67,6 @@ const Home: React.FC<PageProps<IndexPageData>> = ({ data, location }) => {
heading="Flexible and Secure Data Modification" heading="Flexible and Secure Data Modification"
desc="Data Controller for SAS® enables business users to update IT owned source tables, secured by review / approve workflow and full audit trail." desc="Data Controller for SAS® enables business users to update IT owned source tables, secured by review / approve workflow and full audit trail."
> >
<Seo />
<Section color="black" bgColor="white"> <Section color="black" bgColor="white">
<div className="row"> <div className="row">
<div className="col-md-3"> <div className="col-md-3">
@@ -262,3 +261,5 @@ export const pageQuery = graphql`
} }
} }
` `
export const Head = () => <Seo />
@@ -40,7 +40,6 @@ const MarkdownPageTemplate: React.FC<
heading={frontmatter.heading} heading={frontmatter.heading}
desc={frontmatter.desc} desc={frontmatter.desc}
> >
<Seo title={frontmatter.title} />
<Section color="black" bgColor="white" bottomArrow={false}> <Section color="black" bgColor="white" bottomArrow={false}>
<StyledMarkdown dangerouslySetInnerHTML={{ __html: html }} /> <StyledMarkdown dangerouslySetInnerHTML={{ __html: html }} />
</Section> </Section>
@@ -50,6 +49,10 @@ const MarkdownPageTemplate: React.FC<
export default MarkdownPageTemplate export default MarkdownPageTemplate
export const Head: React.FC<{ data: MarkdownPageData }> = ({ data }) => (
<Seo title={data.markdownRemark.frontmatter.title} />
)
export const pageQuery = graphql` export const pageQuery = graphql`
query ($id: String!) { query ($id: String!) {
markdownRemark(id: { eq: $id }) { markdownRemark(id: { eq: $id }) {
+2 -1
View File
@@ -69,7 +69,6 @@ const BlogListTemplate: React.FC<PageProps<BlogListData, PageContext>> = ({
heading="Data Controllers Knowledge Base" heading="Data Controllers Knowledge Base"
desc="A section dedicated to news, updates and educational pieces." desc="A section dedicated to news, updates and educational pieces."
> >
<Seo title="Blog" />
<Section color="black" bgColor="white" bottomArrow={false}> <Section color="black" bgColor="white" bottomArrow={false}>
<div className="row"> <div className="row">
<div className="col-md-7"> <div className="col-md-7">
@@ -126,3 +125,5 @@ export const pageQuery = graphql`
} }
} }
` `
export const Head = () => <Seo title="Blog" />
+15 -8
View File
@@ -14,17 +14,9 @@ const BlogPostTemplate: React.FC<
PageProps<{ post: PostNode }, PageContext> PageProps<{ post: PostNode }, PageContext>
> = ({ data, location, pageContext }) => { > = ({ data, location, pageContext }) => {
const { post } = data const { post } = data
const { previewImg } = post.frontmatter
return ( return (
<Layout location={location} heroSection={false}> <Layout location={location} heroSection={false}>
<Seo
title={post.frontmatter.title}
description={post.frontmatter.description}
previewImg={
previewImg?.childImageSharp?.gatsbyImageData?.images?.fallback?.src
}
/>
<Section color="black" bgColor="white" bottomArrow={false}> <Section color="black" bgColor="white" bottomArrow={false}>
<div className="row"> <div className="row">
<div className="col-md-7"> <div className="col-md-7">
@@ -41,6 +33,21 @@ const BlogPostTemplate: React.FC<
export default BlogPostTemplate export default BlogPostTemplate
export const Head: React.FC<{ data: { post: PostNode } }> = ({ data }) => {
const { post } = data
const { previewImg } = post.frontmatter
return (
<Seo
title={post.frontmatter.title}
description={post.frontmatter.description}
previewImg={
previewImg?.childImageSharp?.gatsbyImageData?.images?.fallback?.src
}
/>
)
}
export const pageQuery = graphql` export const pageQuery = graphql`
query PostByPath($id: String!) { query PostByPath($id: String!) {
post: markdownRemark(id: { eq: $id }) { post: markdownRemark(id: { eq: $id }) {
+2 -1
View File
@@ -71,7 +71,6 @@ const BlogSearchTemplate: React.FC<PageProps<SearchData, PageContext>> = ({
heading="Data Controllers Knowledge Base" heading="Data Controllers Knowledge Base"
desc="A section dedicated to news, updates and educational pieces." desc="A section dedicated to news, updates and educational pieces."
> >
<Seo title="Blog" />
<Section color="black" bgColor="white" bottomArrow={false}> <Section color="black" bgColor="white" bottomArrow={false}>
<div className="row"> <div className="row">
<div className="col-md-7"> <div className="col-md-7">
@@ -96,3 +95,5 @@ const BlogSearchTemplate: React.FC<PageProps<SearchData, PageContext>> = ({
} }
export default BlogSearchTemplate export default BlogSearchTemplate
export const Head = () => <Seo title="Blog" />
+2 -1
View File
@@ -68,7 +68,6 @@ const FeedListTemplate: React.FC<PageProps<FeedListData, PageContext>> = ({
heading="Data Controllers Feed" heading="Data Controllers Feed"
desc="Announcements and social media style updates from the Data Controller team." desc="Announcements and social media style updates from the Data Controller team."
> >
<Seo title="Feed" />
<Section color="black" bgColor="white" bottomArrow={false}> <Section color="black" bgColor="white" bottomArrow={false}>
<div className="row"> <div className="row">
<div className="col-md-7"> <div className="col-md-7">
@@ -129,3 +128,5 @@ export const pageQuery = graphql`
} }
} }
` `
export const Head = () => <Seo title="Feed" />
+15 -8
View File
@@ -14,17 +14,9 @@ const FeedPostTemplate: React.FC<
PageProps<{ post: PostNode }, PageContext> PageProps<{ post: PostNode }, PageContext>
> = ({ data, location, pageContext }) => { > = ({ data, location, pageContext }) => {
const { post } = data const { post } = data
const { previewImg } = post.frontmatter
return ( return (
<Layout location={location} heroSection={false}> <Layout location={location} heroSection={false}>
<Seo
title={post.frontmatter.title}
description={post.frontmatter.description}
previewImg={
previewImg?.childImageSharp?.gatsbyImageData?.images?.fallback?.src
}
/>
<Section color="black" bgColor="white" bottomArrow={false}> <Section color="black" bgColor="white" bottomArrow={false}>
<div className="row"> <div className="row">
<div className="col-md-7"> <div className="col-md-7">
@@ -45,6 +37,21 @@ const FeedPostTemplate: React.FC<
export default FeedPostTemplate export default FeedPostTemplate
export const Head: React.FC<{ data: { post: PostNode } }> = ({ data }) => {
const { post } = data
const { previewImg } = post.frontmatter
return (
<Seo
title={post.frontmatter.title}
description={post.frontmatter.description}
previewImg={
previewImg?.childImageSharp?.gatsbyImageData?.images?.fallback?.src
}
/>
)
}
export const pageQuery = graphql` export const pageQuery = graphql`
query FeedPostByPath($id: String!) { query FeedPostByPath($id: String!) {
post: markdownRemark(id: { eq: $id }) { post: markdownRemark(id: { eq: $id }) {