diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..4043f6e4ab --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# Configuration of code ownership and review approvals for the binary-com/deriv-app repo. +# +# More info: https://help.github.com/articles/about-codeowners/ +# + +* @binary-com/write-admin-smartcharts diff --git a/.github/actions/checkout/action.yml b/.github/actions/checkout/action.yml new file mode 100644 index 0000000000..ddccf418bc --- /dev/null +++ b/.github/actions/checkout/action.yml @@ -0,0 +1,68 @@ +name: Checkout + +inputs: + fetch-depth: + default: 1 + required: false + type: number + path: + required: false + type: string + repository: + default: ${{ github.repository }} + required: false + type: string + ref: + required: false + type: string + alternate_repository: + required: false + type: string + alternate_ref: + required: false + type: string + token: + default: ${{ github.token }} + required: false + type: string + +outputs: + ref_exists: + description: 'Specifies whether the ref exists or not' + value: ${{ steps.repo.outputs.ref-exists }} + +runs: + using: composite + + steps: + - id: repo + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + run: | + if git ls-remote --heads --quiet --exit-code https://${{ inputs.token }}@github.com/${{ inputs.repository }}.git ${{ inputs.ref }} + then + echo "::notice::Checkout: ${{ inputs.repository }} using ${{ inputs.ref }}" + echo "ref-exists=true" >> $GITHUB_OUTPUT + else + echo "::notice::Checkout: ${{ inputs.repository }} does not have ref ${{ inputs.ref }} (fallback to ${{ inputs.alternate_ref }})" + echo "ref-exists=false" >> $GITHUB_OUTPUT + fi + + - if: steps.repo.outputs.ref-exists == 'true' + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + with: + fetch-depth: ${{ inputs.fetch-depth }} + path: ${{ inputs.path }} + repository: ${{ inputs.repository }} + ref: ${{ inputs.ref }} + token: ${{ inputs.token }} + + - if: steps.repo.outputs.ref-exists == 'false' + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + with: + fetch-depth: ${{ inputs.fetch-depth }} + path: ${{ inputs.path }} + repository: ${{ inputs.alternate_repository }} + ref: ${{ inputs.alternate_ref }} + token: ${{ inputs.token }} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..a276517bb5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: "/" + schedule: + interval: daily + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/generate_and_push_deriv_charts.yml b/.github/workflows/generate_and_push_deriv_charts.yml index f4cdc0eac7..c34397f672 100644 --- a/.github/workflows/generate_and_push_deriv_charts.yml +++ b/.github/workflows/generate_and_push_deriv_charts.yml @@ -9,7 +9,7 @@ jobs: steps: - name: Setup node and npm id: step1 - uses: actions/setup-node@v2 + uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a with: node-version: '18' check-latest: true @@ -18,7 +18,7 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} - name: Checkout repo id: step2 - uses: actions/checkout@v3 + uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 with: fetch-depth: 1 ref: master diff --git a/.github/workflows/generate_preview_link.yml b/.github/workflows/generate_preview_link.yml new file mode 100644 index 0000000000..ef704a9875 --- /dev/null +++ b/.github/workflows/generate_preview_link.yml @@ -0,0 +1,128 @@ +name: Generate preview link + +on: + pull_request_target: + types: [opened, synchronize] + +concurrency: + group: cloudflare-pages-build-${{ github.head_ref }} + cancel-in-progress: true + +jobs: + build_and_deploy_preview_link: + runs-on: Ubuntu-latest + permissions: + checks: write + pull-requests: write + steps: + - name: Verify user + uses: "deriv-com/shared-actions/.github/actions/verify_user_in_organization@v1" + with: + username: ${{github.event.pull_request.user.login}} + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + + - name: Post preview build comment + id: post_preview_build_comment + uses: 'deriv-com/shared-actions/.github/actions/post_preview_build_comment@v1' + with: + issue_number: ${{github.event.number}} + head_sha: ${{github.event.pull_request.head.sha}} + + - name: Checkout SmartCharts + uses: 'binary-com/SmartCharts/.github/actions/checkout@master' + with: + repository: ${{github.event.pull_request.user.login}}/SmartCharts + path: SmartCharts + ref: ${{github.head_ref}} + alternate_repository: 'binary-com/SmartCharts' + alternate_ref: master + + - name: Checkout deriv-app + id: deriv_app + uses: 'binary-com/SmartCharts/.github/actions/checkout@master' + with: + repository: ${{github.event.pull_request.user.login}}/deriv-app + path: deriv-app + ref: ${{github.head_ref}} + alternate_repository: 'binary-com/deriv-app' + alternate_ref: master + + - name: Custom flutter-chart + id: flutter_chart + uses: 'binary-com/SmartCharts/.github/actions/checkout@master' + with: + repository: ${{github.event.pull_request.user.login}}/flutter-chart + path: flutter-chart + ref: ${{github.head_ref}} + alternate_repository: 'regentmarkets/flutter-chart' + alternate_ref: fe-changes + token: ${{ secrets.REPO_READ_TOKEN }} + + - uses: subosito/flutter-action@v2 + with: + flutter-version: '3.10.6' + channel: 'stable' + cache: true + + - name: Add SSH key + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_KEY }}" > ~/.ssh/github_action_key + chmod 600 ~/.ssh/github_action_key + + - name: Build flutter + env: + GIT_SSH_COMMAND: 'ssh -i ~/.ssh/github_action_key' + run: | + cd SmartCharts/chart_app + flutter pub get + flutter build web --web-renderer html --release + + - name: Setup Node + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 + with: + node-version: 18.x + + - name: Setup deriv-app + run: cd deriv-app && npm install && npm run bootstrap + + - name: Remove @deriv-charts in deriv-app + run: rm -rf deriv-app/node_modules/@deriv/deriv-charts/dist + + - name: Setup SmartCharts + run: cd SmartCharts && npm install + + - name: Build SmartCharts + run: cd SmartCharts && npm run build -- --output-path ../deriv-app/node_modules/@deriv/deriv-charts/dist + + - name: Run Tests + run: cd SmartCharts && npm run test + + - name: Build deriv-app + env: + NODE_ENV: 'production' + run: cd deriv-app && npm run build:all + + - name: Setup Node + uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a + with: + node-version: 20 + + - name: Publish to CF pages branch + id: publish_to_pages_branch + uses: 'deriv-com/shared-actions/.github/actions/publish_to_pages_branch@v1' + with: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_TEST_LINKS_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_TEST_LINKS_ACCOUNT_ID }} + project_name: 'smartcharts-preview' + branch_name: pr-${{github.event.number}}${{ steps.deriv_app.outputs.ref_exists == 'true' && '-dtra' || ''}}${{ steps.flutter_chart.outputs.ref_exists == 'true' && '-flutt' || ''}} + output_dir: deriv-app/packages/core/dist + + - name: 'Generate preview link comment' + if: always() && steps.post_preview_build_comment.outcome == 'success' + uses: 'deriv-com/shared-actions/.github/actions/post_preview_link_comment@v1' + with: + issue_number: ${{github.event.number}} + check_run_id: ${{steps.post_preview_build_comment.outputs.check_run_id}} + preview_url: ${{steps.publish_to_pages_branch.outputs.cf_pages_url}} + status: ${{job.status}} diff --git a/.github/workflows/release-automation.yml b/.github/workflows/release-automation.yml deleted file mode 100644 index a1f7464eff..0000000000 --- a/.github/workflows/release-automation.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Release automation for SmartCharts -permissions: - pull-requests: write -on: - workflow_dispatch: - inputs: - tag: - description: 'Tag' - required: true - skip_circleci_checks: - description: 'Skip checking CircleCI workflow' - type: boolean - required: false - skip_pending_checks: - description: 'Skip waiting for pull request checks' - type: boolean - required: false - - -concurrency: - group: release_automation_group - -jobs: - release_issues: - permissions: write-all - runs-on: ubuntu-latest - steps: - - name: Checkout to repo - uses: actions/checkout@v3 - with: - ref: master - - name: Setup node - uses: actions/setup-node@v2 - - name: Wait for logs to accumulate - run: | - sleep 10 - - name: Release issues in SmartCharts - uses: binary-com/fe-toolbox@production_V20230602_0 - with: - tag: ${{ inputs.tag }} - list_id: ${{ secrets.LIST_ID }} - platform: 'deriv-charts' - release_tags_list_id: ${{ secrets.RELEASE_TAGS_LIST_ID }} - regression_testing_template_id: ${{ secrets.REGRESSION_TESTING_TEMPLATE_ID }} - circleci_project_slug: 'gh/binary-com/SmartCharts' - circleci_workflow_name: 'release_staging' - skip_circleci_checks: ${{ inputs.skip_circleci_checks }} - skip_pending_checks: ${{ inputs.skip_pending_checks }} - CIRCLECI_TOKEN: ${{ secrets.CIRCLECI_TOKEN }} - CLICKUP_API_TOKEN: ${{ secrets.CLICKUP_API_TOKEN }} - SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }} - SLACK_USER_TOKEN: ${{ secrets.SLACK_USER_TOKEN }} - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml deleted file mode 100644 index da54cf213e..0000000000 --- a/.github/workflows/tag-release.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Tag for SmartCharts release - -permissions: write-all - -on: - workflow_dispatch: - inputs: - tag: - description: 'Tag' - required: true - -jobs: - create-tag: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Create and release tag - run: | - git config --global user.name "DerivFE" - git config --global user.email "fe-access@deriv.com" - - git tag ${{ inputs.tag }} ${{ github.ref }} - git push origin ${{ inputs.tag }} diff --git a/chart_app/lib/src/models/indicators.dart b/chart_app/lib/src/models/indicators.dart index 9797f94479..5d6df46cdb 100644 --- a/chart_app/lib/src/models/indicators.dart +++ b/chart_app/lib/src/models/indicators.dart @@ -309,7 +309,7 @@ class IndicatorsModel { values: values, )); } else if (item is AlligatorSeries) { - //TODO : enable offset after fixing in the flutter charts + //TO DO : enable offset after fixing in the flutter charts tooltipContent.add(JsIndicatorTooltip( name: AlligatorIndicatorConfig.name, values: [ @@ -368,6 +368,16 @@ class IndicatorsModel { double y, int bottomIndicatorIndex, ) { + /// Called to get epoch from x position + int? _getClosestEpoch(int? x, int granularity) { + try { + return getClosestEpoch?.call(x, granularity); + // ignore: avoid_catches_without_on_clauses + } catch (_) { + return null; + } + } + int configIndex = 0; Series? bottomItemIndicator; final List sortedSeriesList = [...seriesList]; @@ -388,9 +398,11 @@ class IndicatorsModel { final Offset target = Offset(x, y); final int? epoch = - getClosestEpoch?.call(controller.getEpochFromX(x), granularity); + _getClosestEpoch(controller.getEpochFromX(x), granularity); - if (bottomItemIndicator != null && bottomIndicatorIndex != null) { + if (bottomItemIndicator != null && + bottomIndicatorIndex > -1 && + epoch != null) { if (bottomItemIndicator is AwesomeOscillatorSeries) { final List aoEntries = bottomItemIndicator.entries ?? []; @@ -816,11 +828,11 @@ class IndicatorsModel { } } } else if (item is AlligatorSeries) { - final List teethEntries = item.teethSeries!.entries ?? []; + final List teethEntries = item.teethSeries?.entries ?? []; - final List jawEntries = item.jawSeries!.entries ?? []; + final List jawEntries = item.jawSeries?.entries ?? []; - final List lipEntries = item.lipsSeries!.entries ?? []; + final List lipEntries = item.lipsSeries?.entries ?? []; if (isPointOnIndicator( teethEntries, @@ -864,7 +876,7 @@ class IndicatorsModel { if (index != null) { final int quoteIndex = index - offset; - if (quoteIndex <= entries.length - 1) { + if (quoteIndex <= entries.length - 1 && quoteIndex > 0) { final Tick prevIndex = entries[quoteIndex - 1]; final Tick currIndex = entries[quoteIndex]; diff --git a/package-lock.json b/package-lock.json index b02e38538f..7d5727fc7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@deriv/deriv-charts", - "version": "2.0.2", + "version": "2.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@deriv/deriv-charts", - "version": "2.0.2", + "version": "2.0.4", "license": "ISC", "dependencies": { "@types/lodash.set": "^4.3.7", diff --git a/package.json b/package.json index d0451cf0ea..cb4f474416 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@deriv/deriv-charts", - "version": "2.0.2", + "version": "2.0.4", "main": "dist/smartcharts.js", "author": "amin@binary.com", "contributors": [ diff --git a/sass/components/_barrier.scss b/sass/components/_barrier.scss index ecadd37dbf..98632b3c83 100644 --- a/sass/components/_barrier.scss +++ b/sass/components/_barrier.scss @@ -8,6 +8,7 @@ top: 0; user-select: none; width: 100%; + z-index: 1; } /* Drag Lines and Prices */ .chart-line { @@ -159,7 +160,7 @@ background-color: $color-blue; height: 24px; opacity: 0.3; - position: relative; + position: absolute; right: -10px; top: -12px; } @@ -205,7 +206,6 @@ right: 0; height: 120px !important; opacity: 0.3; - transition: all 0.1s ease-out; } /* css gradients are only partially supported in Safari, but will work here since colors are not premultiplied */ diff --git a/sass/styles/chart.scss b/sass/styles/chart.scss index d312549868..8701f1ed82 100644 --- a/sass/styles/chart.scss +++ b/sass/styles/chart.scss @@ -1103,6 +1103,9 @@ $spot-color: #0298d3; } } } + .ciq-chart { + padding: 0 8px; + } } // To hide google translate tooltips on drag diff --git a/scripts/deriv-app-deploy.sh b/scripts/deriv-app-deploy.sh deleted file mode 100644 index 3ad4470911..0000000000 --- a/scripts/deriv-app-deploy.sh +++ /dev/null @@ -1,6 +0,0 @@ -npm run build -rm -rf node_modules && rm .*rc *.js *.ts && rm -rf deriv-app -git clone https://github.com/binary-com/deriv-app.git --depth 1 -cd deriv-app && npm run bootstrap && rm -rf node_modules/@deriv/deriv-charts/dist && cd .. -cp -R dist deriv-app/node_modules/@deriv/deriv-charts/ -cd deriv-app && npm run build:prod diff --git a/src/components/PriceLine.tsx b/src/components/PriceLine.tsx index e75d3692a2..24a3023740 100644 --- a/src/components/PriceLine.tsx +++ b/src/components/PriceLine.tsx @@ -45,7 +45,6 @@ const PriceLine = ({ setDragLine, visible, } = store; - const showBarrier = React.useMemo(() => !(hideOffscreenBarrier && offScreen), [hideOffscreenBarrier, offScreen]); const showBarrierDragLine = React.useMemo( () => !hideBarrierLine && (!hideOffscreenLine || !offScreen) && !isOverlapping, @@ -60,7 +59,7 @@ const PriceLine = ({ if (!showBarrier) return null; const width = priceLineWidth + 12; - const price_right_offset = (isOverlappingWithPriceLine ? width - overlappedBarrierWidth : 0) + (isMobile ? 20 : 4); + const price_right_offset = (isOverlappingWithPriceLine ? width - overlappedBarrierWidth + 6 : 0) + (isMobile ? 20 : 3); return (
)}
diff --git a/src/feed/Feed.ts b/src/feed/Feed.ts index 0eca8d6cd8..142c7a0f8b 100644 --- a/src/feed/Feed.ts +++ b/src/feed/Feed.ts @@ -362,6 +362,10 @@ class Feed { end: number, callback: TPaginationCallback ) { + if (this._mainStore.state.hasReachedEndOfData) { + return; + } + const isMainChart = true; // TODO There is no need to get historical data before startTime if (this.startEpoch /* && start < this.startEpoch */ || (this.endEpoch && end > this.endEpoch)) { @@ -407,6 +411,12 @@ class Feed { callback({ moreAvailable: false, quotes: [] }); this.setHasReachedEndOfData(true); } + + if (result.quotes?.length && result.quotes.length < count) { + callback({ moreAvailable: false, quotes: result.quotes }); + this.setHasReachedEndOfData(true); + return; + } } catch (err) { console.error(err); result = { error: err }; diff --git a/src/flutter-chart/index.ts b/src/flutter-chart/index.ts index 98412020d0..0739db1380 100644 --- a/src/flutter-chart/index.ts +++ b/src/flutter-chart/index.ts @@ -12,6 +12,8 @@ export const createChartElement = ({ onChartLoad }: TCreateChartElementProps) => return; } + setupListeners(); + const flutterChartElement = document.createElement('div'); flutterChartElement.classList.add('flutter-chart'); @@ -42,6 +44,19 @@ export const createChartElement = ({ onChartLoad }: TCreateChartElementProps) => return flutterChartElement; }; +const setupListeners = () => { + const listener = (ev: KeyboardEvent) => { + // To fix a trackjs issue caused by some keyboard events that don't contain `key` or `code` props. + // https://github.com/flutter/engine/blob/f20657354d8b53baafcec55650830ead89adf3e9/lib/web_ui/lib/src/engine/keyboard_binding.dart#L386 + if (!ev.key || !ev.code) { + ev.stopImmediatePropagation(); + } + }; + + window.addEventListener('keydown', listener, true); + window.addEventListener('keyup', listener, true); +}; + export const runChartApp = () => { if (window._flutter.initState.isMounted) { window._flutter.appRunner?.runApp(); diff --git a/src/store/BarrierStore.ts b/src/store/BarrierStore.ts index 5f7d7ae6eb..d2462ac057 100644 --- a/src/store/BarrierStore.ts +++ b/src/store/BarrierStore.ts @@ -113,6 +113,8 @@ export default class BarrierStore { () => [this.mainStore.chartAdapter.epochBounds, this.mainStore.chartAdapter.quoteBounds], this._drawShadedArea ); + + this.mainStore.chartAdapter.painter.registerCallback(this._drawShadedArea); }; init(): void { @@ -195,6 +197,9 @@ export default class BarrierStore { this.disposeDrawReaction?.(); + this.mainStore.chartAdapter.painter.unregisterCallback(this._drawShadedArea); + + const i = this.mainStore.chart._barriers.findIndex((b: BarrierStore) => b === this); if (i !== -1) { this.mainStore.chart._barriers.splice(i, 1); diff --git a/src/store/CrosshairStore.ts b/src/store/CrosshairStore.ts index 0a13300c76..92fcb1c4f6 100644 --- a/src/store/CrosshairStore.ts +++ b/src/store/CrosshairStore.ts @@ -334,7 +334,7 @@ class CrosshairStore { if (dsField.constructor === Number) { fieldValue = dsField.toString(); } else if (dsField.constructor === Date) { - fieldValue = moment(dsField).format(this.getDateTimeFormat()); + fieldValue = moment(data.Date).format(this.getDateTimeFormat()); } else { fieldValue = dsField as string; } diff --git a/src/store/TimeperiodStore.ts b/src/store/TimeperiodStore.ts index 572f14c81c..ac85a54cdd 100644 --- a/src/store/TimeperiodStore.ts +++ b/src/store/TimeperiodStore.ts @@ -118,7 +118,7 @@ export default class TimeperiodStore { if (dataSegmentClose && dataSegmentClose.length) { const currentQuote = dataSegmentClose[dataSegmentClose.length - 1]; if (currentQuote.DT) { - const now = this._serverTime.getLocalDate().getTime(); + const now = this._serverTime?.getLocalDate()?.getTime(); const diff = now - currentQuote.DT.getTime(); const granularity = this.mainStore.chart.granularity; diff --git a/src/utils/ServerTime.ts b/src/utils/ServerTime.ts index b95860be2a..4ab03e893a 100644 --- a/src/utils/ServerTime.ts +++ b/src/utils/ServerTime.ts @@ -84,8 +84,7 @@ class ServerTime { if (this.serverTimeAtResponse) { return this.serverTimeAtResponse; } - - throw new Error('Server time is undefined!'); + return getUTCEpoch(new Date()); } getLocalDate() {