Compare commits

..

5 Commits

Author SHA1 Message Date
Jan Prochazka 391d04b45c MPR archive 2025-10-26 18:28:35 +01:00
Jan Prochazka f974c00a63 MPR references 2025-10-26 18:23:07 +01:00
Jan Prochazka 9f0107c002 MPR refs, macros 2025-10-26 18:06:45 +01:00
Jan Prochazka 6ce82e915e MPR grouping 2025-10-26 15:04:13 +01:00
Jan Prochazka 864797fc99 MPR advanced exports 2025-10-26 09:33:48 +01:00
21 changed files with 65 additions and 231 deletions
-12
View File
@@ -9,8 +9,6 @@ const {
putCloudContent,
removeCloudCachedConnection,
getPromoWidgetData,
getPromoWidgetList,
getPromoWidgetPreview,
} = require('../utility/cloudIntf');
const connections = require('./connections');
const socket = require('../utility/socket');
@@ -298,16 +296,6 @@ module.exports = {
return data;
},
promoWidgetList_meta: true,
async promoWidgetList() {
return getPromoWidgetList();
},
promoWidgetPreview_meta: true,
async promoWidgetPreview({ campaign, variant }) {
return getPromoWidgetPreview(campaign, variant);
},
// chatStream_meta: {
// raw: true,
// method: 'post',
-12
View File
@@ -480,16 +480,6 @@ async function getPromoWidgetData() {
return promoWidgetData;
}
async function getPromoWidgetPreview(campaign, variant) {
const resp = await axios.default.get(`${DBGATE_CLOUD_URL}/premium-promo-widget-preview/${campaign}/${variant}`);
return resp.data;
}
async function getPromoWidgetList() {
const resp = await axios.default.get(`${DBGATE_CLOUD_URL}/promo-widget-list`);
return resp.data;
}
module.exports = {
createDbGateIdentitySession,
startCloudTokenChecking,
@@ -508,6 +498,4 @@ module.exports = {
readCloudTestTokenHolder,
getPublicIpInfo,
getPromoWidgetData,
getPromoWidgetPreview,
getPromoWidgetList,
};
+3 -1
View File
@@ -39,7 +39,8 @@ export class TableGridDisplay extends GridDisplay {
public getDictionaryDescription: DictionaryDescriptionFunc = null,
isReadOnly = false,
public isRawMode = false,
public currentSettings = null
public currentSettings = null,
public areReferencesAllowed = true
) {
super(config, setConfig, cache, setCache, driver, dbinfo, serverVersion, currentSettings);
@@ -248,6 +249,7 @@ export class TableGridDisplay extends GridDisplay {
}
processReferences(select: Select, displayedColumnInfo: DisplayedColumnInfo, options) {
if (!this.areReferencesAllowed) return;
this.addJoinsFromExpandedColumns(select, this.columns, 'basetbl', displayedColumnInfo);
if (!options.isExport && this.displayOptions.showHintColumns) {
this.addHintsToSelect(select);
-5
View File
@@ -35,11 +35,6 @@
background: linear-gradient(135deg, #1686c8, #8a25b1);
}
.premium-gradient {
background: linear-gradient(135deg, #1686c8, #8a25b1);
color: white;
}
.web-color-primary {
background: #1686c8;
}
@@ -468,12 +468,14 @@ await dbgateApi.executeQuery(${JSON.stringify(
{ divider: true },
isSqlOrDoc &&
isProApp() &&
!connection.isReadOnly &&
hasPermission(`dbops/import`) && {
onClick: handleImport,
text: _t('database.import', { defaultMessage: 'Import' }),
},
isSqlOrDoc &&
isProApp() &&
hasPermission(`dbops/export`) && {
onClick: handleExport,
text: _t('database.export', { defaultMessage: 'Export' }),
+1 -38
View File
@@ -8,7 +8,6 @@ import {
getCloudSigninTokenHolder,
getExtensions,
getVisibleToolbar,
promoWidgetPreview,
visibleToolbar,
visibleWidgetSideBar,
} from '../stores';
@@ -51,7 +50,6 @@ import { isProApp } from '../utility/proTools';
import { openWebLink } from '../utility/simpleTools';
import { _t } from '../translations';
import ExportImportConnectionsModal from '../modals/ExportImportConnectionsModal.svelte';
import { getBoolSettingsValue } from '../settings/settingsTools';
// function themeCommand(theme: ThemeDefinition) {
// return {
@@ -691,7 +689,7 @@ registerCommand({
name: 'Export database',
toolbar: true,
icon: 'icon export',
testEnabled: () => getCurrentDatabase() != null && hasPermission(`dbops/export`),
testEnabled: () => getCurrentDatabase() != null && hasPermission(`dbops/export`) && isProApp(),
onClick: () => {
openImportExportTab({
targetStorageType: getDefaultFileFormat(getExtensions()).storageType,
@@ -1166,41 +1164,6 @@ registerCommand({
onClick: () => currentDatabase.set(null),
});
let loadedCampaignList = [];
registerCommand({
id: 'internal.loadCampaigns',
category: 'Internal',
name: 'Load campaign list',
testEnabled: () => getBoolSettingsValue('internal.showCampaigns', false),
onClick: async () => {
const resp = await apiCall('cloud/promo-widget-list', {});
loadedCampaignList = resp;
},
});
registerCommand({
id: 'internal.showCampaigns',
category: 'Internal',
name: 'Show campaigns',
testEnabled: () => getBoolSettingsValue('internal.showCampaigns', false) && loadedCampaignList?.length > 0,
getSubCommands: () => {
return loadedCampaignList.map(campaign => ({
text: `${campaign.campaignName} (${campaign.countries || 'Global'}) - #${campaign.quantileRank ?? '*'}/${
campaign.quantileGroupCount ?? '*'
} - ${campaign.variantIdentifier}`,
onClick: async () => {
promoWidgetPreview.set(
await apiCall('cloud/promo-widget-preview', {
campaign: campaign.campaignIdentifier,
variant: campaign.variantIdentifier,
})
);
},
}));
},
});
const electron = getElectron();
if (electron) {
electron.addEventListener('run-command', (e, commandId) => runCommand(commandId));
@@ -12,6 +12,7 @@
import { showModal } from '../modals/modalTools';
import DefineDictionaryDescriptionModal from '../modals/DefineDictionaryDescriptionModal.svelte';
import { sleep } from '../utility/common';
import { isProApp } from '../utility/proTools';
export let column;
export let conid = undefined;
@@ -72,29 +73,35 @@
column.foreignKey && [{ divider: true }, { onClick: openReferencedTable, text: column.foreignKey.refTableName }],
setGrouping && { divider: true },
setGrouping && { onClick: () => setGrouping('GROUP'), text: 'Group by' },
setGrouping && { onClick: () => setGrouping('MAX'), text: 'MAX' },
setGrouping && { onClick: () => setGrouping('MIN'), text: 'MIN' },
setGrouping && { onClick: () => setGrouping('SUM'), text: 'SUM' },
setGrouping && { onClick: () => setGrouping('AVG'), text: 'AVG' },
setGrouping && { onClick: () => setGrouping('COUNT'), text: 'COUNT' },
setGrouping && { onClick: () => setGrouping('COUNT DISTINCT'), text: 'COUNT DISTINCT' },
isProApp() &&
setGrouping && [
{ divider: true },
{ onClick: () => setGrouping('GROUP'), text: 'Group by' },
{ onClick: () => setGrouping('MAX'), text: 'MAX' },
{ onClick: () => setGrouping('MIN'), text: 'MIN' },
{ onClick: () => setGrouping('SUM'), text: 'SUM' },
{ onClick: () => setGrouping('AVG'), text: 'AVG' },
{ onClick: () => setGrouping('COUNT'), text: 'COUNT' },
{ onClick: () => setGrouping('COUNT DISTINCT'), text: 'COUNT DISTINCT' },
],
isTypeDateTime(column.dataType) && [
{ divider: true },
{ onClick: () => setGrouping('GROUP:YEAR'), text: 'Group by YEAR' },
{ onClick: () => setGrouping('GROUP:MONTH'), text: 'Group by MONTH' },
{ onClick: () => setGrouping('GROUP:DAY'), text: 'Group by DAY' },
],
isProApp() &&
isTypeDateTime(column.dataType) && [
{ divider: true },
{ onClick: () => setGrouping('GROUP:YEAR'), text: 'Group by YEAR' },
{ onClick: () => setGrouping('GROUP:MONTH'), text: 'Group by MONTH' },
{ onClick: () => setGrouping('GROUP:DAY'), text: 'Group by DAY' },
],
{ divider: true },
allowDefineVirtualReferences && { onClick: handleDefineVirtualForeignKey, text: 'Define virtual foreign key' },
column.foreignKey && {
onClick: handleCustomizeDescriptions,
text: 'Customize description',
},
isProApp() &&
allowDefineVirtualReferences && { onClick: handleDefineVirtualForeignKey, text: 'Define virtual foreign key' },
column.foreignKey &&
isProApp() && {
onClick: handleCustomizeDescriptions,
text: 'Customize description',
},
];
}
</script>
@@ -7,6 +7,7 @@
import { showModal } from '../modals/modalTools';
import ColumnEditorModal from '../tableeditor/ColumnEditorModal.svelte';
import { editorDeleteColumn } from 'dbgate-tools';
import { isProApp } from '../utility/proTools';
export let column;
export let display;
@@ -59,13 +60,17 @@
on:mouseup
>
<div>
<span class="expandColumnIcon" style={`margin-right: ${5 + (column.uniquePath.length - 1) * 10}px`}>
<FontIcon
icon={column.isExpandable ? plusExpandIcon(display.isExpandedColumn(column.uniqueName)) : 'icon invisible-box'}
on:click={() => display.toggleExpandedColumn(column.uniqueName)}
data-testid="ColumnManagerRow_expand_{column.uniqueName}"
/>
</span>
{#if isProApp()}
<span class="expandColumnIcon" style={`margin-right: ${5 + (column.uniquePath.length - 1) * 10}px`}>
<FontIcon
icon={column.isExpandable
? plusExpandIcon(display.isExpandedColumn(column.uniqueName))
: 'icon invisible-box'}
on:click={() => display.toggleExpandedColumn(column.uniqueName)}
data-testid="ColumnManagerRow_expand_{column.uniqueName}"
/>
</span>
{/if}
{#if isJsonView}
<FontIcon icon="img column" />
{:else}
+3 -2
View File
@@ -68,6 +68,7 @@
import registerCommand from '../commands/registerCommand';
import { registerMenu } from '../utility/contextMenu';
import { getLocalStorage, setLocalStorage } from '../utility/storageCache';
import { isProApp } from '../utility/proTools';
export let config;
export let setConfig;
@@ -205,7 +206,7 @@
name="references"
height="30%"
collapsed={isDetailView}
skip={!(showReferences && display?.hasReferences)}
skip={!(showReferences && display?.hasReferences && isProApp())}
data-testid="DataGrid_itemReferences"
>
<ReferenceManager {...$$props} {managerSize} />
@@ -214,7 +215,7 @@
<WidgetColumnBarItem
title="Macros"
name="macros"
skip={!showMacros}
skip={!(showMacros && isProApp())}
collapsed={!expandMacros}
data-testid="DataGrid_itemMacros"
>
@@ -30,6 +30,7 @@
import SqlFormView from '../formview/SqlFormView.svelte';
import { getBoolSettingsValue } from '../settings/settingsTools';
import { getDictionaryDescription } from '../utility/dictionaryDescriptionTools';
import { isProApp } from '../utility/proTools';
export let conid;
export let database;
@@ -82,7 +83,8 @@
extendedDbInfo?.tables?.find(x => x.pureName == pureName && x.schemaName == schemaName)
?.tablePermissionRole == 'read',
isRawMode,
$settingsValue
$settingsValue,
isProApp()
)
: null;
@@ -1,29 +1,22 @@
<script lang="ts">
import JsonUiCountdown from './JsonUiCountdown.svelte';
import JsonUiHeading from './JsonUiHeading.svelte';
import JsonUiHighlight from './JsonUiHighlight.svelte';
import JsonUiLinkButton from './JsonUiLinkButton.svelte';
import JsonUiMarkdown from './JsonUiMarkdown.svelte';
import JsonUiTextBlock from './JsonUiTextBlock.svelte';
import JsonUiTickList from './JsonUiTickList.svelte';
import { JsonUiBlock } from './jsonuitypes';
export let blocks: JsonUiBlock[] = [];
export let passProps = {};
const componentMap = {
text: JsonUiTextBlock,
heading: JsonUiHeading,
ticklist: JsonUiTickList,
button: JsonUiLinkButton,
markdown: JsonUiMarkdown,
highlight: JsonUiHighlight,
countdown: JsonUiCountdown,
} as const;
</script>
{#each blocks as block, i}
{#if block.type in componentMap}
<svelte:component this={componentMap[block.type]} {...block} {...passProps} />
<svelte:component this={componentMap[block.type]} {...block} />
{/if}
{/each}
@@ -1,73 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
export let colorClass: string = 'premium-gradient';
export let validTo;
function formatRemaining(validTo, now) {
let diffMs = validTo.getTime() - now.getTime();
if (diffMs <= 0) return '0 minutes';
const totalMinutes = Math.floor(diffMs / 60000);
const days = Math.floor(totalMinutes / (24 * 60));
const hours = Math.floor((totalMinutes % (24 * 60)) / 60);
const minutes = totalMinutes % 60;
const parts = [];
const en = (n, unit) => ({
num: n,
unit: n == 1 ? unit : unit + 's',
});
if (days) parts.push(en(days, 'day'));
if (hours) parts.push(en(hours, 'hour'));
// Always include minutes to report down to minutes
parts.push(en(minutes, 'minute'));
return parts;
}
let currentDate = new Date();
onMount(() => {
const interval = setInterval(() => {
currentDate = new Date();
}, 5000);
return () => {
clearInterval(interval);
};
});
$: parts = formatRemaining(new Date(validTo), currentDate);
</script>
{#if validTo}
<div class="countdown {colorClass}">
<span class="big">Offer ends in:</span><br />
{#each parts as part}
<span class="part">
<span class="big">{part.num}</span>
{part.unit}
</span>
{/each}
</div>
{/if}
<style>
.countdown {
text-align: center;
margin: 10px;
border: 1px solid;
padding: 5px;
}
.big {
font-size: large;
font-weight: bold;
}
.part {
margin: 0 5px;
}
</style>
@@ -1,19 +0,0 @@
<script lang="ts">
export let text: string;
export let colorClass: string = 'premium-gradient';
</script>
<div class="highlight {colorClass}">
{text}
</div>
<style>
.highlight {
text-align: center;
margin: 10px;
font-size: large;
font-weight: bold;
border: 1px solid;
padding: 5px;
}
</style>
@@ -1,15 +0,0 @@
<script lang="ts">
import Markdown from '../elements/Markdown.svelte';
export let text: string;
</script>
<div>
<Markdown source={text} />
</div>
<style>
div {
margin: 10px;
}
</style>
@@ -94,6 +94,7 @@
title: 'Export database',
description: 'Export to file like CSV, JSON, Excel, or other DB',
command: 'database.export',
isProFeature: true,
testid: 'NewObjectModal_databaseExport',
disabledMessage: 'Export is not available for current database',
},
-3
View File
@@ -187,9 +187,6 @@ export const seenPremiumPromoWidget = writableWithStorage(null, 'seenPremiumProm
export const cloudConnectionsStore = writable({});
export const promoWidgetPreview = writable(null);
export const DEFAULT_OBJECT_SEARCH_SETTINGS = {
pureName: true,
schemaName: false,
@@ -1,11 +1,12 @@
import type { QuickExportDefinition } from 'dbgate-types';
import { currentArchive, getCurrentArchive, getExtensions } from '../stores';
import hasPermission from './hasPermission';
import { isProApp } from './proTools';
export function createQuickExportMenuItems(handler: (fmt: QuickExportDefinition) => Function, advancedExportMenuItem) {
const extensions = getExtensions();
return [
{
isProApp() && {
text: 'Export advanced...',
...advancedExportMenuItem,
},
@@ -15,7 +16,7 @@ export function createQuickExportMenuItems(handler: (fmt: QuickExportDefinition)
onClick: handler(fmt),
})),
{ divider: true },
{
isProApp() && {
text: 'Current archive',
onClick: handler({
extension: 'jsonl',
@@ -1,17 +1,14 @@
<script lang="ts">
import JsonUiContentRenderer from '../jsonui/JsonUiContentRenderer.svelte';
import { promoWidgetPreview } from '../stores';
import { usePromoWidget } from '../utility/metadataLoaders';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
const promoWidget = usePromoWidget({});
$: promoWidgetData = $promoWidgetPreview || $promoWidget;
</script>
<WidgetsInnerContainer>
{#if promoWidgetData?.state == 'data'}
<JsonUiContentRenderer blocks={promoWidgetData?.blocks} passProps={{ validTo: promoWidgetData?.validTo }} />
{#if $promoWidget?.state == 'data'}
<JsonUiContentRenderer blocks={$promoWidget?.blocks} />
{/if}
</WidgetsInnerContainer>
+2 -1
View File
@@ -27,6 +27,7 @@
import { useConnectionColor } from '../utility/useConnectionColor';
import { apiCall } from '../utility/api';
import { statusBarTabInfo } from '../utility/statusBarStore';
import { isProApp } from '../utility/proTools';
$: databaseName = $currentDatabase && $currentDatabase.name;
$: connection = $currentDatabase && $currentDatabase.connection;
@@ -155,7 +156,7 @@
</div>
</div>
{/if}
{#if $currentArchive && $currentArchive != 'default'}
{#if isProApp() && $currentArchive && $currentArchive != 'default'}
<div
class="item flex clickable"
title="Current archive"
@@ -11,6 +11,7 @@
import PublicCloudWidget from './PublicCloudWidget.svelte';
import PrivateCloudWidget from './PrivateCloudWidget.svelte';
import hasPermission from '../utility/hasPermission';
import { isProApp } from '../utility/proTools';
</script>
{#if hasPermission('widgets/database')}
@@ -22,7 +23,7 @@
{#if $visibleSelectedWidget == 'history' && hasPermission('widgets/history')}
<HistoryWidget />
{/if}
{#if $visibleSelectedWidget == 'archive' && hasPermission('widgets/archive')}
{#if $visibleSelectedWidget == 'archive' && hasPermission('widgets/archive') && isProApp()}
<ArchiveWidget />
{/if}
{#if $visibleSelectedWidget == 'plugins' && hasPermission('widgets/plugins')}
@@ -11,7 +11,6 @@
getCurrentConfig,
cloudSigninTokenHolder,
seenPremiumPromoWidget,
promoWidgetPreview,
} from '../stores';
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
import hasPermission from '../utility/hasPermission';
@@ -61,7 +60,7 @@
name: 'history',
title: 'Query history & Closed tabs',
},
{
isProApp() && {
icon: 'icon archive',
name: 'archive',
title: 'Archive (saved tabular data)',
@@ -168,8 +167,6 @@
openWebLink(url, true);
}
}
$: promoWidgetData = $promoWidgetPreview || $promoWidget;
</script>
<div class="main">
@@ -180,7 +177,7 @@
{/if}
{#each widgets
.filter(x => x && hasPermission(`widgets/${x.name}`))
.filter(x => !x.isPremiumPromo || (!isProApp() && promoWidgetData?.state == 'data'))
.filter(x => !x.isPremiumPromo || (!isProApp() && $promoWidget?.state == 'data'))
// .filter(x => !x.isPremiumOnly || isProApp())
.filter(x => x.name != 'cloud-private' || $cloudSigninTokenHolder) as item}
<div
@@ -189,7 +186,7 @@
data-testid={`WidgetIconPanel_${item.name}`}
on:click={() => handleChangeWidget(item.name)}
>
{#if item.isPremiumPromo && promoWidgetData?.isColoredIcon}
{#if item.isPremiumPromo && $promoWidget?.isColoredIcon}
<FontIcon
icon={item.icon}
title={item.title}
@@ -200,7 +197,7 @@
{/if}
{#if item.isPremiumPromo}
<div class="premium-promo">Premium</div>
{#if promoWidgetData?.identifier != $seenPremiumPromoWidget}
{#if $promoWidget?.identifier != $seenPremiumPromoWidget}
<div class="premium-promo-not-seen">•</div>
{/if}
{/if}