Skip to content

Commit f44669c

Browse files
authored
add "Passive Mining" dialog on mining finished (#2595)
* Create 20260130124036_passive_mining.sql * Update MinePanel.vue * refactor autoextract tag * Update 20260130124036_passive_mining.sql * rename file * add returrn type * i18n * use "Passive Mining" instead of "Auto Extract" * use "Passive Mining" instead of "Auto Extract" * prettier
1 parent 237d4b4 commit f44669c

File tree

11 files changed

+130
-13
lines changed

11 files changed

+130
-13
lines changed

backend/src/controllers/mining.controller.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { User } from '@supabase/supabase-js';
22
import { NextFunction, Request, Response } from 'express';
33
import { decode } from 'jsonwebtoken';
44
import ENV from '../config';
5+
import { Contacts } from '../db/interfaces/Contacts';
56
import {
67
MiningSources,
78
OAuthMiningSourceProvider
89
} from '../db/interfaces/MiningSources';
10+
import RedisQueuedEmailsCache from '../services/cache/redis/RedisQueuedEmailsCache';
911
import { ContactFormat } from '../services/extractors/engines/FileImport';
1012
import TaskManagerFile from '../services/tasks-manager/TaskManagerFile';
1113
import TasksManager from '../services/tasks-manager/TasksManager';
@@ -15,7 +17,9 @@ import { ImapAuthError } from '../utils/errors';
1517
import validateType from '../utils/helpers/validation';
1618
import logger from '../utils/logger';
1719
import redis from '../utils/redis';
20+
import RedisStreamProducer from '../utils/streams/redis/RedisStreamProducer';
1821
import supabaseClient from '../utils/supabase';
22+
import { EmailVerificationData } from '../workers/email-verification/emailVerificationHandlers';
1923
import {
2024
generateErrorObjectFromImapError,
2125
getValidImapLogin,
@@ -27,10 +31,6 @@ import {
2731
getTokenWithScopeValidation,
2832
validateFileContactsData
2933
} from './mining.helpers';
30-
import RedisStreamProducer from '../utils/streams/redis/RedisStreamProducer';
31-
import { EmailVerificationData } from '../workers/email-verification/emailVerificationHandlers';
32-
import { Contacts } from '../db/interfaces/Contacts';
33-
import RedisQueuedEmailsCache from '../services/cache/redis/RedisQueuedEmailsCache';
3434

3535
/**
3636
* Exchanges an OAuth authorization code for tokens and extracts user email
@@ -275,7 +275,8 @@ export default function initializeMiningController(
275275

276276
const sources = sourcesData.map((s) => ({
277277
email: s.email,
278-
type: s.type
278+
type: s.type,
279+
passive_mining: s.passive_mining
279280
}));
280281

281282
return res.status(200).send({

backend/src/db/interfaces/MiningSources.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface MiningSources {
3131
email: string;
3232
type: MiningSourceType;
3333
credentials: ImapMiningSourceCredentials | OAuthMiningSourceCredentials;
34+
passive_mining: boolean;
3435
}[]
3536
>;
3637
getCredentialsBySourceEmail(

backend/src/db/pg/PgMiningSources.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { Logger } from 'winston';
33
import {
44
ImapMiningSourceCredentials,
55
MiningSource,
6-
MiningSourceType,
76
MiningSources,
7+
MiningSourceType,
88
OAuthMiningSourceCredentials
99
} from '../interfaces/MiningSources';
1010

@@ -16,7 +16,7 @@ export default class PgMiningSources implements MiningSources {
1616
DO UPDATE SET credentials=excluded.credentials,type=excluded.type;`;
1717

1818
private static readonly GET_BY_USER_SQL = `
19-
SELECT email, type, pgp_sym_decrypt(credentials, $1) as credentials
19+
SELECT email, type, pgp_sym_decrypt(credentials, $1) as credentials, passive_mining
2020
FROM private.mining_sources
2121
WHERE user_id = $2;`;
2222

@@ -59,6 +59,7 @@ export default class PgMiningSources implements MiningSources {
5959
email: string;
6060
type: MiningSourceType;
6161
credentials: ImapMiningSourceCredentials | OAuthMiningSourceCredentials;
62+
passive_mining: boolean;
6263
}[]
6364
> {
6465
try {
@@ -70,6 +71,7 @@ export default class PgMiningSources implements MiningSources {
7071
email: string;
7172
type: MiningSourceType;
7273
credentials: ImapMiningSourceCredentials | OAuthMiningSourceCredentials;
74+
passive_mining: boolean;
7375
}[];
7476
} catch (error) {
7577
if (error instanceof Error) {

frontend/src/app.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,16 @@
5959
</div>
6060
</template>
6161
</Toast>
62+
63+
<PassiveMiningDialog />
6264
</template>
6365

6466
<script setup lang="ts">
67+
import PassiveMiningDialog from '@/components/Mining/PassiveMiningDialog.vue';
6568
import { useIdle } from '@vueuse/core';
6669
import { reloadNuxtApp } from 'nuxt/app';
67-
import { signOutManually } from './utils/auth';
6870
import Normalizer from '~/utils/normalizer';
71+
import { signOutManually } from './utils/auth';
6972
7073
const $user = useSupabaseUser();
7174
const $leadminerStore = useLeadminerStore();
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<template>
2+
<Dialog
3+
v-model:visible="$leadminerStore.passiveMiningDialog"
4+
modal
5+
:header="t('header')"
6+
class="w-full sm:w-[35rem]"
7+
>
8+
<p>
9+
{{ t('paragraph_1') }} <br />
10+
{{ t('paragraph_2') }}
11+
</p>
12+
<template #footer>
13+
<div class="flex flex-col sm:flex-row justify-between w-full gap-2">
14+
<Button
15+
:label="t('yes_enable')"
16+
class="w-full sm:w-auto"
17+
@click="enablePassiveMining()"
18+
/>
19+
<Button
20+
:label="$t('common.cancel')"
21+
class="w-full sm:w-auto"
22+
severity="secondary"
23+
@click="closePassiveMiningDialog()"
24+
/>
25+
</div>
26+
</template>
27+
</Dialog>
28+
</template>
29+
30+
<script setup lang="ts">
31+
import type { MiningSource } from '~/types/mining';
32+
33+
const miningSource = ref<MiningSource>();
34+
35+
const $leadminerStore = useLeadminerStore();
36+
const $supabase = useSupabaseClient();
37+
38+
const { t } = useI18n({
39+
useScope: 'local',
40+
});
41+
42+
watch(
43+
() => $leadminerStore.passiveMiningDialog,
44+
(newVal) => {
45+
if (newVal) {
46+
miningSource.value = $leadminerStore.activeMiningSource;
47+
}
48+
},
49+
);
50+
51+
function closePassiveMiningDialog() {
52+
$leadminerStore.passiveMiningDialog = false;
53+
}
54+
55+
async function enablePassiveMining() {
56+
if (miningSource.value) {
57+
const { error } = await $supabase
58+
// @ts-expect-error: Issue with nuxt/supabase
59+
.schema('private')
60+
.from('mining_sources')
61+
.update({ passive_mining: true })
62+
.match({ email: miningSource.value.email });
63+
64+
if (error) {
65+
throw error;
66+
}
67+
}
68+
closePassiveMiningDialog();
69+
}
70+
</script>
71+
72+
<i18n lang="json">
73+
{
74+
"en": {
75+
"header": "Continuous Contact Extraction",
76+
"paragraph_1": "Enable continuous contact extraction from future emails?",
77+
"paragraph_2": "New contacts found in incoming emails will be automatically saved.",
78+
"yes_enable": "Yes, enable"
79+
},
80+
"fr": {
81+
"header": "Extraction continue des contacts",
82+
"paragraph_1": "Activer l'extraction continue des contacts à partir des futurs e-mails ?",
83+
"paragraph_2": "Les nouveaux contacts trouvés dans les e-mails entrants seront automatiquement enregistrés.",
84+
"yes_enable": "Oui, activer"
85+
}
86+
}
87+
</i18n>

frontend/src/components/Mining/StepperPanels/MinePanel.vue

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ const { t: $t } = useI18n({
9797
});
9898
9999
const { miningSource } = defineProps<{
100-
miningSource: MiningSource;
100+
miningSource: MiningSource | undefined;
101101
}>();
102102
103103
const sourceType = computed(() => $leadminerStore.miningType);
@@ -206,7 +206,7 @@ onMounted(async () => {
206206
} catch (error: any) {
207207
if (error?.statusCode === 502 || error?.statusCode === 503) {
208208
$stepper.prev();
209-
} else {
209+
} else if (miningSource) {
210210
$consentSidebar.show(miningSource.type, miningSource.email, '/mine');
211211
}
212212
}
@@ -249,10 +249,12 @@ watch(extractionFinished, async (finished) => {
249249
});
250250
$stepper.next();
251251
} else if (finished) {
252+
if (miningSource && !miningSource.passive_mining)
253+
$leadminerStore.passiveMiningDialog = true;
252254
$toast.add({
253255
severity: 'info',
254256
summary: t('mining_done'),
255-
detail: totalExtractedNotificationMessage,
257+
detail: totalExtractedNotificationMessage.value,
256258
group: 'achievement',
257259
life: 8000,
258260
});

frontend/src/stores/leadminer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export const useLeadminerStore = defineStore('leadminer', () => {
6464
activeMiningTask.value || isLoadingBoxes.value || activeEnrichment.value,
6565
);
6666

67+
const passiveMiningDialog = ref(false);
68+
6769
const miningStartedAndFinished = computed(() =>
6870
Boolean(miningStartedAt.value && miningCompleted.value),
6971
);
@@ -104,6 +106,8 @@ export const useLeadminerStore = defineStore('leadminer', () => {
104106

105107
miningType.value = 'email';
106108

109+
passiveMiningDialog.value = false;
110+
107111
errors.value = {};
108112
}
109113

@@ -538,6 +542,7 @@ export const useLeadminerStore = defineStore('leadminer', () => {
538542
miningCompleted,
539543
activeMiningTask,
540544
activeTask,
545+
passiveMiningDialog,
541546
miningStartedAndFinished,
542547
miningInterrupted,
543548
errors,

frontend/src/types/mining.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface MiningSource {
1313
type: MiningSourceType;
1414
email: string;
1515
isValid?: boolean;
16+
passive_mining: boolean;
1617
}
1718

1819
export interface MiningProgress {

frontend/src/utils/oauth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export async function addOAuthAccount(
2424
* Redirects to the OAuth consent error page.
2525
* @returns {Promise<string>} The URL of the OAuth consent error page with provider and referrer parameters.
2626
*/
27-
export async function redirectOauthConsentPage() {
27+
export async function redirectOauthConsentPage(): Promise<string> {
2828
const provider = useLeadminerStore().activeMiningSource?.type;
2929
const referrer = (await useSupabaseClient().auth.getSession()).data.session
3030
?.user.id;

frontend/src/utils/sources.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function updateMiningSourcesValidity(
1717
* @param {MiningSource} current - The current mining source being processed.
1818
* @returns {MiningSource} The updated mining source.
1919
*/
20-
function updateValidity(current: MiningSource) {
20+
function updateValidity(current: MiningSource): MiningSource {
2121
if (current.email === activeMiningSource?.email) {
2222
current.isValid = isValid;
2323
}

0 commit comments

Comments
 (0)