<commit_before>
'use strict';

const os = require('os');
const https = require('https');
const proc = require('child_process');
const StateController = require('../../lib/state-controller');
const OSXSupport = require('../../lib/support/osx');

const {
  fakeProcesses, fakeKiteInstallPaths, fakeResponse,
  withKiteInstalled, withKiteRunning, withKiteNotRunning,
  withFakeServer, withKiteEnterpriseInstalled, withBothKiteInstalled,
  withKiteEnterpriseRunning, withKiteEnterpriseNotRunning,
} = require('../spec-helpers.js');

describe('StateController - OSX Support', () => {
  beforeEach(() => {
    spyOn(os, 'platform').andReturn('darwin');
    spyOn(os, 'release').andReturn('14.0.0');
  });

  fakeKiteInstallPaths();

  describe('.isKiteSupported()', () => {
    it('returns a resolved promise', () => {
      waitsForPromise(() => StateController.isKiteSupported());
    });

    describe('when the os release is below 10.10', () => {
      beforeEach(() => {
        os.release.andReturn('13.0.0');
      });

      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.isKiteSupported());
      });
    });
  });

  describe('.isKiteInstalled()', () => {
    withKiteInstalled(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.isKiteInstalled());
      });
    });

    describe('when kite is not installed', () => {
      it('returns a rejected promise', () => {
        waitsForPromise({
          shouldReject: true,
        }, () => StateController.isKiteInstalled());
      });
    });

    withKiteEnterpriseInstalled(() => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () =>
          StateController.isKiteInstalled());
      });
    });
  });

  describe('.isKiteEnterpriseInstalled()', () => {
    describe('when kite enterprise is not installed', () => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () =>
          StateController.isKiteEnterpriseInstalled());
      });
    });

    withKiteInstalled(() => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () =>
          StateController.isKiteEnterpriseInstalled());
      });
    });

    withKiteEnterpriseInstalled(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.isKiteEnterpriseInstalled());
      });
    });
  });

  describe('.hasBothKiteInstalled()', () => {
    describe('when no kite is installed', () => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () =>
        StateController.hasBothKiteInstalled());
      });
    });

    describe('when kite enterprise is not installed', () => {
      withKiteInstalled(() => {
        it('returns a rejected promise', () => {
          waitsForPromise({shouldReject: true}, () =>
          StateController.hasBothKiteInstalled());
        });
      });
    });

    describe('when kite is not installed', () => {
      withKiteEnterpriseInstalled(() => {
        it('returns a rejected promise', () => {
          waitsForPromise({shouldReject: true}, () =>
          StateController.hasBothKiteInstalled());
        });
      });
    });

    withBothKiteInstalled(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.hasBothKiteInstalled());
      });
    });
  });

  describe('.installKite()', () => {
    describe('when every command succeeds', () => {
      beforeEach(() => {
        fakeProcesses({
          hdiutil: () => 0,
          cp: () => 0,
          rm: () => 0,
          mdfind: (ps, args) => {
            const [, key] = args[0].split(/\s=\s/);
            key === '"com.kite.Kite"'
              ? ps.stdout('/Applications/Kite.app')
              : ps.stdout('');
            return 0;
          },
        });
      });

      it('returns a resolved promise', () => {
        const options = {
          onInstallStart: jasmine.createSpy(),
          onMount: jasmine.createSpy(),
          onCopy: jasmine.createSpy(),
          onUnmount: jasmine.createSpy(),
          onRemove: jasmine.createSpy(),
        };

        waitsForPromise(() => StateController.installKite(options));
        runs(() => {
          expect(proc.spawn).toHaveBeenCalledWith('hdiutil', [
            'attach', '-nobrowse',
            OSXSupport.KITE_DMG_PATH,
          ]);
          expect(proc.spawn).toHaveBeenCalledWith('cp', [
            '-r',
            OSXSupport.KITE_APP_PATH.mounted,
            OSXSupport.APPS_PATH,
          ]);
          expect(proc.spawn).toHaveBeenCalledWith('hdiutil', [
            'detach',
            OSXSupport.KITE_VOLUME_PATH,
          ]);
          expect(proc.spawn).toHaveBeenCalledWith('rm', [
            OSXSupport.KITE_DMG_PATH,
          ]);
          expect(proc.spawn).toHaveBeenCalledWith('mdfind', [
            'kMDItemCFBundleIdentifier = "com.kite.Kite"',
          ]);

          expect(options.onInstallStart).toHaveBeenCalled();
          expect(options.onMount).toHaveBeenCalled();
          expect(options.onCopy).toHaveBeenCalled();
          expect(options.onUnmount).toHaveBeenCalled();
          expect(options.onRemove).toHaveBeenCalled();
        });
      });
    });

    describe('when mounting the archive fails', () => {
      beforeEach(() => {
        fakeProcesses({
          hdiutil: () => 1,
          cp: () => 0,
          rm: () => 0,
        });
      });

      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.installKite());
      });
    });

    describe('when copying the archive content fails', () => {
      beforeEach(() => {
        fakeProcesses({
          hdiutil: () => 0,
          cp: () => 1,
          rm: () => 0,
        });
      });

      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.installKite());
      });
    });

    describe('when unmounting the archive fails', () => {
      beforeEach(() => {
        fakeProcesses({
          hdiutil: (ps, [command]) => command === 'attach' ? 0 : 1,
          cp: () => 0,
          rm: () => 0,
        });
      });

      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.installKite());
      });
    });

    describe('when removing the downloaded archive fails', () => {
      beforeEach(() => {
        fakeProcesses({
          hdiutil: () => 0,
          cp: () => 0,
          rm: () => 1,
        });
      });

      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.installKite());
      });
    });
  });

  describe('.downloadKite()', () => {
    withFakeServer([
      [
        o => /^http:\/\/kite\.com/.test(o),
        o => fakeResponse(303, '', {headers: {location: 'https://download.kite.com'}}),
      ], [
        o => /^https:\/\/download\.kite\.com/.test(o),
        o => fakeResponse(200, 'foo'),
      ],
    ], () => {
      describe('when the download succeeds', () => {
        beforeEach(() => {
          fakeProcesses({
            hdiutil: () => 0,
            cp: () => 0,
            rm: () => 0,
          });
        });

        describe('with the install option', () => {
          beforeEach(() => {
            fakeProcesses({
              mdfind: (ps, args) => {
                const [, key] = args[0].split(/\s=\s/);
                key === '"com.kite.Kite"'
                  ? ps.stdout('/Applications/Kite.app')
                  : ps.stdout('');
                return 0;
              },
            });
          })
          it('returns a promise resolved after the install', () => {
            const options = {
              install: true,
              onDownload: jasmine.createSpy(),
              onInstallStart: jasmine.createSpy(),
              onMount: jasmine.createSpy(),
              onCopy: jasmine.createSpy(),
              onUnmount: jasmine.createSpy(),
              onRemove: jasmine.createSpy(),
            };
            const url = 'http://kite.com/download';

            waitsForPromise(() => StateController.downloadKite(url, options));
            runs(() => {
              expect(https.request).toHaveBeenCalledWith('http://kite.com/download');
              expect(proc.spawn).toHaveBeenCalledWith('hdiutil', [
                'attach', '-nobrowse',
                OSXSupport.KITE_DMG_PATH,
              ]);
              expect(proc.spawn).toHaveBeenCalledWith('cp', [
                '-r',
                OSXSupport.KITE_APP_PATH.mounted,
                OSXSupport.APPS_PATH,
              ]);
              expect(proc.spawn).toHaveBeenCalledWith('hdiutil', [
                'detach',
                OSXSupport.KITE_VOLUME_PATH,
              ]);
              expect(proc.spawn).toHaveBeenCalledWith('rm', [
                OSXSupport.KITE_DMG_PATH,
              ]);

              expect(options.onDownload).toHaveBeenCalled();
              expect(options.onInstallStart).toHaveBeenCalled();
              expect(options.onMount).toHaveBeenCalled();
              expect(options.onCopy).toHaveBeenCalled();
              expect(options.onUnmount).toHaveBeenCalled();
              expect(options.onRemove).toHaveBeenCalled();
            });
          });
        });
      });
    });
  });

  describe('.isKiteRunning()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.isKiteRunning());
      });
    });

    withKiteInstalled(() => {
      describe('but not running', () => {
        beforeEach(() => {
          fakeProcesses({
            '/bin/ps': (ps) => {
              ps.stdout('');
              return 0;
            },
          });
        });

        it('returns a rejected promise', () => {
          waitsForPromise({shouldReject: true}, () => StateController.isKiteRunning());
        });
      });

      withKiteRunning(() => {
        it('returns a resolved promise', () => {
          waitsForPromise(() => StateController.isKiteRunning());
        });
      });
    });
  });

  describe('.canRunKite()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.canRunKite());
      });
    });

    withKiteNotRunning(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.canRunKite());
      });
    });

    withKiteRunning(() => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.canRunKite());
      });
    });
  });

  describe('.runKite()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.runKite());
      });
    });

    withKiteRunning(() => {
      it('returns a resolved function', () => {
        waitsForPromise(() => StateController.runKite());
      });
    });

    withKiteNotRunning(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.runKite());
        runs(() => {
          expect(proc.spawn).toHaveBeenCalledWith('defaults', [
            'write', 'com.kite.Kite', 'shouldReopenSidebar', '0',
          ]);

          expect(proc.spawn).toHaveBeenCalledWith('open', [
            '-a', OSXSupport.installPath,
          ]);
        });
      });
    });
  });

  describe('.isKiteEnterpriseRunning()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.isKiteEnterpriseRunning());
      });
    });

    withKiteEnterpriseInstalled(() => {
      describe('but not running', () => {
        beforeEach(() => {
          fakeProcesses({
            '/bin/ps': (ps) => {
              ps.stdout('');
              return 0;
            },
          });
        });

        it('returns a rejected promise', () => {
          waitsForPromise({shouldReject: true}, () => StateController.isKiteEnterpriseRunning());
        });
      });

      withKiteEnterpriseRunning(() => {
        it('returns a resolved promise', () => {
          waitsForPromise(() => StateController.isKiteEnterpriseRunning());
        });
      });
    });
  });

  describe('.canRunKiteEnterprise()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.canRunKiteEnterprise());
      });
    });

    withKiteEnterpriseNotRunning(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.canRunKiteEnterprise());
      });
    });

    withKiteEnterpriseRunning(() => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.canRunKiteEnterprise());
      });
    });
  });

  describe('.runKiteEnterprise()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.runKiteEnterprise());
      });
    });

    withKiteEnterpriseRunning(() => {
      it('returns a resolved function', () => {
        waitsForPromise(() => StateController.runKiteEnterprise());
      });
    });

    withKiteEnterpriseNotRunning(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.runKiteEnterprise());
        runs(() => {
          expect(proc.spawn).toHaveBeenCalledWith('defaults', [
            'write', 'enterprise.kite.Kite', 'shouldReopenSidebar', '0',
          ]);

          expect(proc.spawn).toHaveBeenCalledWith('open', [
            '-a', OSXSupport.enterpriseInstallPath,
          ]);
        });
      });
    });
  });
});
<commit_msg>fix download test<commit_after>
'use strict';

const os = require('os');
const https = require('https');
const proc = require('child_process');
const StateController = require('../../lib/state-controller');
const OSXSupport = require('../../lib/support/osx');

const {
  fakeProcesses, fakeKiteInstallPaths, fakeResponse,
  withKiteInstalled, withKiteRunning, withKiteNotRunning,
  withFakeServer, withKiteEnterpriseInstalled, withBothKiteInstalled,
  withKiteEnterpriseRunning, withKiteEnterpriseNotRunning,
} = require('../spec-helpers.js');

describe('StateController - OSX Support', () => {
  beforeEach(() => {
    spyOn(os, 'platform').andReturn('darwin');
    spyOn(os, 'release').andReturn('14.0.0');
  });

  fakeKiteInstallPaths();

  describe('.isKiteSupported()', () => {
    it('returns a resolved promise', () => {
      waitsForPromise(() => StateController.isKiteSupported());
    });

    describe('when the os release is below 10.10', () => {
      beforeEach(() => {
        os.release.andReturn('13.0.0');
      });

      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.isKiteSupported());
      });
    });
  });

  describe('.isKiteInstalled()', () => {
    withKiteInstalled(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.isKiteInstalled());
      });
    });

    describe('when kite is not installed', () => {
      it('returns a rejected promise', () => {
        waitsForPromise({
          shouldReject: true,
        }, () => StateController.isKiteInstalled());
      });
    });

    withKiteEnterpriseInstalled(() => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () =>
          StateController.isKiteInstalled());
      });
    });
  });

  describe('.isKiteEnterpriseInstalled()', () => {
    describe('when kite enterprise is not installed', () => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () =>
          StateController.isKiteEnterpriseInstalled());
      });
    });

    withKiteInstalled(() => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () =>
          StateController.isKiteEnterpriseInstalled());
      });
    });

    withKiteEnterpriseInstalled(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.isKiteEnterpriseInstalled());
      });
    });
  });

  describe('.hasBothKiteInstalled()', () => {
    describe('when no kite is installed', () => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () =>
        StateController.hasBothKiteInstalled());
      });
    });

    describe('when kite enterprise is not installed', () => {
      withKiteInstalled(() => {
        it('returns a rejected promise', () => {
          waitsForPromise({shouldReject: true}, () =>
          StateController.hasBothKiteInstalled());
        });
      });
    });

    describe('when kite is not installed', () => {
      withKiteEnterpriseInstalled(() => {
        it('returns a rejected promise', () => {
          waitsForPromise({shouldReject: true}, () =>
          StateController.hasBothKiteInstalled());
        });
      });
    });

    withBothKiteInstalled(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.hasBothKiteInstalled());
      });
    });
  });

  describe('.installKite()', () => {
    describe('when every command succeeds', () => {
      beforeEach(() => {
        fakeProcesses({
          hdiutil: () => 0,
          cp: () => 0,
          rm: () => 0,
          mdfind: (ps, args) => {
            const [, key] = args[0].split(/\s=\s/);
            key === '"com.kite.Kite"'
              ? ps.stdout('/Applications/Kite.app')
              : ps.stdout('');
            return 0;
          },
        });
      });

      it('returns a resolved promise', () => {
        const options = {
          onInstallStart: jasmine.createSpy(),
          onMount: jasmine.createSpy(),
          onCopy: jasmine.createSpy(),
          onUnmount: jasmine.createSpy(),
          onRemove: jasmine.createSpy(),
        };

        waitsForPromise(() => StateController.installKite(options));
        runs(() => {
          expect(proc.spawn).toHaveBeenCalledWith('hdiutil', [
            'attach', '-nobrowse',
            OSXSupport.KITE_DMG_PATH,
          ]);
          expect(proc.spawn).toHaveBeenCalledWith('cp', [
            '-r',
            OSXSupport.KITE_APP_PATH.mounted,
            OSXSupport.APPS_PATH,
          ]);
          expect(proc.spawn).toHaveBeenCalledWith('hdiutil', [
            'detach',
            OSXSupport.KITE_VOLUME_PATH,
          ]);
          expect(proc.spawn).toHaveBeenCalledWith('rm', [
            OSXSupport.KITE_DMG_PATH,
          ]);
          expect(proc.spawn).toHaveBeenCalledWith('mdfind', [
            'kMDItemCFBundleIdentifier = "com.kite.Kite"',
          ]);

          expect(options.onInstallStart).toHaveBeenCalled();
          expect(options.onMount).toHaveBeenCalled();
          expect(options.onCopy).toHaveBeenCalled();
          expect(options.onUnmount).toHaveBeenCalled();
          expect(options.onRemove).toHaveBeenCalled();
        });
      });
    });

    describe('when mounting the archive fails', () => {
      beforeEach(() => {
        fakeProcesses({
          hdiutil: () => 1,
          cp: () => 0,
          rm: () => 0,
        });
      });

      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.installKite());
      });
    });

    describe('when copying the archive content fails', () => {
      beforeEach(() => {
        fakeProcesses({
          hdiutil: () => 0,
          cp: () => 1,
          rm: () => 0,
        });
      });

      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.installKite());
      });
    });

    describe('when unmounting the archive fails', () => {
      beforeEach(() => {
        fakeProcesses({
          hdiutil: (ps, [command]) => command === 'attach' ? 0 : 1,
          cp: () => 0,
          rm: () => 0,
        });
      });

      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.installKite());
      });
    });

    describe('when removing the downloaded archive fails', () => {
      beforeEach(() => {
        fakeProcesses({
          hdiutil: () => 0,
          cp: () => 0,
          rm: () => 1,
        });
      });

      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.installKite());
      });
    });
  });

  describe('.downloadKite()', () => {
    withFakeServer([
      [
        o => /^http:\/\/kite\.com/.test(o),
        o => fakeResponse(303, '', {headers: {location: 'https://download.kite.com'}}),
      ], [
        o => /^https:\/\/download\.kite\.com/.test(o),
        o => fakeResponse(200, 'foo'),
      ],
    ], () => {
      describe('when the download succeeds', () => {
        beforeEach(() => {
          fakeProcesses({
            hdiutil: () => 0,
            cp: () => 0,
            rm: () => 0,
            mdfind: (ps) => {
              ps.stdout('');
              return 0;
            },
          });
        });

        describe('with the install option', () => {
          it('returns a promise resolved after the install', () => {
            const options = {
              install: true,
              onDownload: jasmine.createSpy(),
              onInstallStart: jasmine.createSpy(),
              onMount: jasmine.createSpy(),
              onCopy: jasmine.createSpy(),
              onUnmount: jasmine.createSpy(),
              onRemove: () => {
                fakeProcesses({
                  mdfind: (ps, args) => {
                    const [, key] = args[0].split(/\s=\s/);
                    key === '"com.kite.Kite"'
                      ? ps.stdout('/Applications/Kite.app')
                      : ps.stdout('');
                    return 0;
                  },
                });
              },
            };
            const url = 'http://kite.com/download';

            waitsForPromise(() => StateController.downloadKite(url, options));
            runs(() => {
              expect(https.request).toHaveBeenCalledWith('http://kite.com/download');
              expect(proc.spawn).toHaveBeenCalledWith('hdiutil', [
                'attach', '-nobrowse',
                OSXSupport.KITE_DMG_PATH,
              ]);
              expect(proc.spawn).toHaveBeenCalledWith('cp', [
                '-r',
                OSXSupport.KITE_APP_PATH.mounted,
                OSXSupport.APPS_PATH,
              ]);
              expect(proc.spawn).toHaveBeenCalledWith('hdiutil', [
                'detach',
                OSXSupport.KITE_VOLUME_PATH,
              ]);
              expect(proc.spawn).toHaveBeenCalledWith('rm', [
                OSXSupport.KITE_DMG_PATH,
              ]);

              expect(options.onDownload).toHaveBeenCalled();
              expect(options.onInstallStart).toHaveBeenCalled();
              expect(options.onMount).toHaveBeenCalled();
              expect(options.onCopy).toHaveBeenCalled();
              expect(options.onUnmount).toHaveBeenCalled();
              expect(options.onRemove).toHaveBeenCalled();
            });
          });
        });
      });
    });
  });

  describe('.isKiteRunning()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.isKiteRunning());
      });
    });

    withKiteInstalled(() => {
      describe('but not running', () => {
        beforeEach(() => {
          fakeProcesses({
            '/bin/ps': (ps) => {
              ps.stdout('');
              return 0;
            },
          });
        });

        it('returns a rejected promise', () => {
          waitsForPromise({shouldReject: true}, () => StateController.isKiteRunning());
        });
      });

      withKiteRunning(() => {
        it('returns a resolved promise', () => {
          waitsForPromise(() => StateController.isKiteRunning());
        });
      });
    });
  });

  describe('.canRunKite()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.canRunKite());
      });
    });

    withKiteNotRunning(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.canRunKite());
      });
    });

    withKiteRunning(() => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.canRunKite());
      });
    });
  });

  describe('.runKite()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.runKite());
      });
    });

    withKiteRunning(() => {
      it('returns a resolved function', () => {
        waitsForPromise(() => StateController.runKite());
      });
    });

    withKiteNotRunning(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.runKite());
        runs(() => {
          expect(proc.spawn).toHaveBeenCalledWith('defaults', [
            'write', 'com.kite.Kite', 'shouldReopenSidebar', '0',
          ]);

          expect(proc.spawn).toHaveBeenCalledWith('open', [
            '-a', OSXSupport.installPath,
          ]);
        });
      });
    });
  });

  describe('.isKiteEnterpriseRunning()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected promise', () => {
        waitsForPromise({shouldReject: true}, () => StateController.isKiteEnterpriseRunning());
      });
    });

    withKiteEnterpriseInstalled(() => {
      describe('but not running', () => {
        beforeEach(() => {
          fakeProcesses({
            '/bin/ps': (ps) => {
              ps.stdout('');
              return 0;
            },
          });
        });

        it('returns a rejected promise', () => {
          waitsForPromise({shouldReject: true}, () => StateController.isKiteEnterpriseRunning());
        });
      });

      withKiteEnterpriseRunning(() => {
        it('returns a resolved promise', () => {
          waitsForPromise(() => StateController.isKiteEnterpriseRunning());
        });
      });
    });
  });

  describe('.canRunKiteEnterprise()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.canRunKiteEnterprise());
      });
    });

    withKiteEnterpriseNotRunning(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.canRunKiteEnterprise());
      });
    });

    withKiteEnterpriseRunning(() => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.canRunKiteEnterprise());
      });
    });
  });

  describe('.runKiteEnterprise()', () => {
    describe('when kite is not installed', () => {
      it('returns a rejected function', () => {
        waitsForPromise({shouldReject: true}, () => StateController.runKiteEnterprise());
      });
    });

    withKiteEnterpriseRunning(() => {
      it('returns a resolved function', () => {
        waitsForPromise(() => StateController.runKiteEnterprise());
      });
    });

    withKiteEnterpriseNotRunning(() => {
      it('returns a resolved promise', () => {
        waitsForPromise(() => StateController.runKiteEnterprise());
        runs(() => {
          expect(proc.spawn).toHaveBeenCalledWith('defaults', [
            'write', 'enterprise.kite.Kite', 'shouldReopenSidebar', '0',
          ]);

          expect(proc.spawn).toHaveBeenCalledWith('open', [
            '-a', OSXSupport.enterpriseInstallPath,
          ]);
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>const moveSpeed = 50; // units per second
const rotationSpeed = (Math.PI / 180) * 120; // radians per second

export default class Car extends THREE.Object3D {
  constructor(props) {
    super();
    const { name, color, size } = props;

    const bodyGeometry = new THREE.BoxGeometry(size, size, size);
    const bodyMaterial = new THREE.MeshLambertMaterial({ color });
    const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
    body.position.set(0, size / 2, 0);

    const arrowDirection = new THREE.Vector3(0, 0, 1).normalize();
    const arrowLength = size;
    const arrowColor = 0xffffff;
    const arrowPosition = new THREE.Vector3(0, 0, 0);
    const arrow = new THREE.ArrowHelper(arrowDirection, arrowPosition, arrowLength, arrowColor);
    body.add(arrow);

    this.add(body);

    this.name = name;
  }

  rotateLeft(delta) {
    const rotationAngle = rotationSpeed * delta;
    this.rotateY(rotationAngle);
  }

  rotateRight(delta) {
    const rotationAngle = rotationSpeed * delta;
    this.rotateY(-rotationAngle);
  }

  // TODO: Make the speed relative to the size of the car
  // For example: speed = size / 50 * forward direction
  moveForward(delta) {
    const distance = moveSpeed * delta;
    const forwardDirection = this.getWorldDirection(); // Note: This is a unit vector
    forwardDirection.multiplyScalar(distance);
    this.position.add(forwardDirection);
  }

  moveBackward(delta) {
    const distance = moveSpeed * delta;
    const backwardDirection = this.getWorldDirection().negate();
    backwardDirection.multiplyScalar(distance);
    this.position.add(backwardDirection);
  }
}
<commit_msg>Make speed relative to size.<commit_after>export default class Car extends THREE.Object3D {
  constructor(props) {
    super();
    const { name, color, size } = props;

    const bodyGeometry = new THREE.BoxGeometry(size, size, size);
    const bodyMaterial = new THREE.MeshLambertMaterial({ color });
    const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
    body.position.set(0, size / 2, 0);

    const arrowDirection = new THREE.Vector3(0, 0, 1).normalize();
    const arrowLength = size;
    const arrowColor = 0xffffff;
    const arrowPosition = new THREE.Vector3(0, 0, 0);
    const arrow = new THREE.ArrowHelper(arrowDirection, arrowPosition, arrowLength, arrowColor);
    body.add(arrow);

    this.add(body);

    this.name = name;

    const speedScale = (50 / size);
    this.moveSpeed = 50 * speedScale; // units per second
    this.rotationSpeed = (Math.PI / 180) * 50 * speedScale; // radians per second
  }

  rotateLeft(delta) {
    const rotationAngle = this.rotationSpeed * delta;
    this.rotateY(rotationAngle);
  }

  rotateRight(delta) {
    const rotationAngle = this.rotationSpeed * delta;
    this.rotateY(-rotationAngle);
  }

  moveForward(delta) {
    const distance = this.moveSpeed * delta;
    const forwardDirection = this.getWorldDirection(); // Note: This is a unit vector
    forwardDirection.multiplyScalar(distance);
    this.position.add(forwardDirection);
  }

  moveBackward(delta) {
    const distance = this.moveSpeed * delta;
    const backwardDirection = this.getWorldDirection().negate();
    backwardDirection.multiplyScalar(distance);
    this.position.add(backwardDirection);
  }
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
  package: 'foam.nanos.approval',
  name: 'ApprovalRequest',
  documentation: 'Approval requests are stored in approvalRequestDAO and' +
  'represent a single approval request for a single user.',

  implements: [
    'foam.nanos.auth.CreatedAware',
    'foam.nanos.auth.CreatedByAware',
    'foam.nanos.auth.LastModifiedAware',
    'foam.nanos.auth.LastModifiedByAware'
  ],

  javaImports: [
    'foam.core.FObject',
    'foam.core.X',
    'foam.dao.DAO',
    'foam.nanos.logger.Logger'
  ],

  requires: [
    'foam.dao.AbstractDAO',
    'foam.nanos.approval.ApprovalStatus'
  ],

  imports: [
    'approvalRequestDAO',
    'ctrl'
  ],

  tableColumns: [
    'id',
    'description',
    'classification',
    'objId',
    'viewReference',
    'approver',
    'status',
    'memo',
    'approve',
    'reject'
  ],

  sections: [
    {
      name: 'basicInformation',
      permissionRequired: true
    },
    {
      name: 'requestDetails'
    },
    {
      name: 'supportDetails'
    },
    {
      name: '_defaultSection',
      permissionRequired: true
    }
  ],

  properties: [
    {
      class: 'Long',
      name: 'id',
      section: '_defaultSection',
      visibility: 'RO',
      documentation: 'Sequence number.'
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.User',
      name: 'approver',
      section: 'requestDetails',
      documentation: `The user that is requested for approval. When set, "group" property is ignored.`,
      tableCellFormatter: function(approver) {
        let self = this;
        this.__subSubContext__.userDAO.find(approver).then((user)=> {
          if ( user && user.group ) {
            self.add(user.group);
          } else {
            self.add(approver);
          }
        });
      },
      visibility: function(approver) {
        return approver ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      },
    },
    {
      class: 'Object',
      javaType: 'Object',
      name: 'objId',
      permissionRequired: true,
      documentation: 'id of the object that needs approval.',
      tableWidth: 150,
      tableCellFormatter: function(objId) {
        let self = this;
        var userId = parseInt(objId);
        if ( !! userId ) {
          this.__subSubContext__.userDAO.find(userId).then(function(a) {

          if ( a != undefined ) {
            self.add(a.toSummary());
          } else {
            self.add(objId);
          }
          }).catch(function(err) {
            self.add(objId);
          });
        }
      }
    },
    {
      class: 'String',
      name: 'daoKey',
      permissionRequired: true,
      documentation: `Used internally in approvalDAO to point where requested object can be found.
      Should not be used to retrieve approval requests for a given objects
      since an object can have multiple requests of different nature.`
    },
    {
      class: 'String',
      name: 'classification',
      label: 'Approval Type',
      section: 'requestDetails',
      tableWidth: 150,
      documentation: `Should be unique to a certain type of requests and created within a single rule.
      For example "IdentityMind Business approval".
      When retrieving approval requests from a dao, do not use daoKey, use classification instead:
      mlang.AND(
        EQ(ApprovalRequest.OBJ_ID, objectId),
        EQ(ApprovalRequest.REQUEST_REFERENCE, "reference")
      )`,
      gridColumns: 4,
      visibility: function(classification) {
        return classification ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Int',
      name: 'points',
      documentation: `Specific to each ApprovalRequest object.
      Determines the weight of the approved request depending on the approver's role.
      Future: populated in approvalRequestDAO pipeline based on configurations.
      Currentely populated as 1.`,
      gridColumns: 4,
      section: 'basicInformation',
      visibility: function(points) {
        return points ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Int',
      name: 'requiredPoints',
      value: 1,
      gridColumns: 4,
      section: 'basicInformation',
      documentation: `Defines how many approvers required and approvers' ranks.
      E.g. when set to 10:
      1) 10 approval requests with "points" set to 1.
      2) 2 approval requests with "points" set to 3 and 1 approval request with "points" set to 5.
      etc.
      Deafults to 1 meaning only one approval of any approver rank is required by default.`,
      visibility: function(requiredPoints) {
        return requiredPoints ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Int',
      name: 'requiredRejectedPoints',
      value: 1,
      gridColumns: 4,
      section: 'basicInformation',
      visibility: function(requiredRejectedPoints) {
        return requiredRejectedPoints ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.Group',
      name: 'group',
      documentation: `When set, each user in the group will receive a request for approval.
      If "approver" property is set, "group" property is ignored.`,
      section: 'supportDetails',
      visibility: function(group) {
        return group ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Enum',
      of: 'foam.nanos.approval.ApprovalStatus',
      name: 'status',
      value: 'REQUESTED',
      section: 'requestDetails',
      javaFactory: 'return foam.nanos.approval.ApprovalStatus.REQUESTED;',
      visibility: 'RO'
    },
    {
      class: 'String',
      name: 'memo',
      view: { class: 'foam.u2.tag.TextArea', rows: 5, cols: 80 },
      documentation: 'Meant to be used for explanation on why request was approved/rejected',
      section: 'supportDetails',
      visibility: function(memo, status) {
        if ( status == net.nanopay.approval.ApprovalStatus.REQUESTED ) {
          return foam.u2.DisplayMode.RW;
        } else if ( memo ) {
          return foam.u2.DisplayMode.R0;
        } else {
          return foam.u2.DisplayMode.HIDDEN;
        }
      }
    },
    {
      class: 'String',
      name: 'description',
      documentation: `Approval request description.`,
      tableWidth: 200,
      section: 'requestDetails',
      visibility: function(description) {
        return description ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
   },
    {
      class: 'String',
      name: 'token',
      documentation: 'token in email for ‘click to approve’.',
      section: 'basicInformation',
      permissionRequired: true
    },
    {
      class: 'DateTime',
      name: 'created',
      section: 'supportDetails',
      gridColumns: 6,
      visibility: function(created) {
        return created ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'DateTime',
      name: 'lastModified',
      gridColumns: 6,
      section: 'supportDetails',
      visibility: function(lastModified) {
        return lastModified ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.User',
      name: 'createdBy',
      section: 'supportDetails',
      permissionRequired: true
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.User',
      name: 'createdByAgent',
      section: 'supportDetails',
      permissionRequired: true
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.User',
      name: 'lastModifiedBy',
      section: 'supportDetails',
      permissionRequired: true
    },
    {
      class: 'String',
      name: 'refObj',
      transient: true,
      expression: function(daoKey, objId) {
        return daoKey + ':' + objId;
      },
      javaGetter: `
        return getDaoKey() + ": " + getObjId();
      `,
      permissionRequired: true
    },
    {
      class: 'String',
      name: 'daoKey_',
      permissionRequired: true,
      factory: function(o, n) {
        var key = this.daoKey;
        var X = this.ctrl.__subContext__;
        // FIXME: change to a better implementation
        if ( ! X[key] ) {
          // if DAO doesn't exist in context, change daoKey from localMyDAO
          // (server-side) to myDAO (accessible on front-end)
          key = key.substring(5, 6).toLowerCase() + key.substring(6);
        }
        return key;
      }
    },
    {
      name: 'referenceObj_',
      permissionRequired: true,
      expression: function() {
        var X = this.ctrl.__subContext__;
        var objId = X[this.daoKey_].of.ID.type === 'Long' ? parseInt(this.objId) : this.objId;

        X[this.daoKey_]
          .find(objId)
          .then((obj) => {
            if (
              // if approvalRequest is rejected don't show ViewReference
              this.status === foam.nanos.approval.ApprovalStatus.REJECTED
              ||
              // if approvalRequest is for remove and has been either approved/rejected
              (this.status === foam.nanos.approval.ApprovalStatus.APPROVED
                &&
              this.operation === foam.nanos.ruler.Operations.REMOVE)
            ) {
              this.referenceObj_ = '';
              return;
            }
            this.referenceObj_ = obj;
          })
          .catch((err) => {
            console.warn(err.message || err);
          });
        return '';
    }
  }
  ],

  methods: [
    {
      name: 'validate',
      args: [
        {
          name: 'x',
          type: 'Context'
        }
      ],
      javaCode: `Logger logger = (Logger) x.get("logger");
DAO dao = (DAO) x.get(getDaoKey());
if ( dao == null ) {
  logger.error(this.getClass().getSimpleName(), "DaoKey not found", getDaoKey());
  throw new RuntimeException("Invalid dao key for the approval request object.");
}
FObject obj = dao.inX(x).find(getObjId());
if ( obj == null ) {
  logger.error(this.getClass().getSimpleName(), "ObjId not found", getObjId());
  throw new RuntimeException("Invalid object id.");
}
      `
    }
  ],

  actions: [
    {
      name: 'approve',
      label: 'Approve',
      section: 'requestDetails',
      code: function() {
        this.status = this.ApprovalStatus.APPROVED;
        this.approvalRequestDAO.put(this);
        this.approvalRequestDAO.cmd(this.AbstractDAO.RESET_CMD);
      },
      tableWidth: 100
    },
    {
      name: 'reject',
      label: 'Reject',
      section: 'requestDetails',
      code: function() {
        this.status = this.ApprovalStatus.REJECTED;
        this.approvalRequestDAO.put(this);
        this.approvalRequestDAO.cmd(this.AbstractDAO.RESET_CMD);
      },
      tableWidth: 100
    },
    {
      name: 'viewReference',
      isDefault: true,
      isAvailable: function(referenceObj_) {
        return !! referenceObj_;
      },
      code: function(X) {
        var obj = this.referenceObj_;
        // If the dif of objects is calculated and stored in Map(obj.propertiesToUpdate),
        // this is for updating object approvals
        if ( obj.propertiesToUpdate ) {
          // then here we created custom view to display these properties
          X.stack.push({
            class: 'foam.nanos.approval.PropertiesToUpdateView',
            propObject: obj.propertiesToUpdate,
            objId: obj.objId,
            daoKey: obj.daoKey,
            title: 'Updated Properties and Changes'
          });
          return;
        }
        // else pass general view with modeled data for display
        // this is for create, deleting object approvals
        X.stack.push({
          class: 'foam.comics.v2.DAOSummaryView',
          data: obj,
          of: obj.cls_,
          config: foam.comics.v2.DAOControllerConfig.create({
            daoKey: this.daoKey_,
            of: obj.cls_,
            editEnabled: false,
            createEnabled: false,
            deleteEnabled: false
          })
        });
      },
      tableWidth: 100
    }
  ]
});
<commit_msg>Forgot about this one<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
  package: 'foam.nanos.approval',
  name: 'ApprovalRequest',
  documentation: 'Approval requests are stored in approvalRequestDAO and' +
  'represent a single approval request for a single user.',

  implements: [
    'foam.nanos.auth.CreatedAware',
    'foam.nanos.auth.CreatedByAware',
    'foam.nanos.auth.LastModifiedAware',
    'foam.nanos.auth.LastModifiedByAware'
  ],

  javaImports: [
    'foam.core.FObject',
    'foam.core.X',
    'foam.dao.DAO',
    'foam.nanos.logger.Logger'
  ],

  requires: [
    'foam.dao.AbstractDAO',
    'foam.nanos.approval.ApprovalStatus'
  ],

  imports: [
    'approvalRequestDAO',
    'ctrl'
  ],

  tableColumns: [
    'id',
    'description',
    'classification',
    'objId',
    'viewReference',
    'approver',
    'status',
    'memo',
    'approve',
    'reject'
  ],

  sections: [
    {
      name: 'basicInformation',
      permissionRequired: true
    },
    {
      name: 'requestDetails'
    },
    {
      name: 'supportDetails'
    },
    {
      name: '_defaultSection',
      permissionRequired: true
    }
  ],

  properties: [
    {
      class: 'Long',
      name: 'id',
      section: '_defaultSection',
      visibility: 'RO',
      documentation: 'Sequence number.'
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.User',
      name: 'approver',
      section: 'requestDetails',
      documentation: `The user that is requested for approval. When set, "group" property is ignored.`,
      tableCellFormatter: function(approver) {
        let self = this;
        this.__subSubContext__.userDAO.find(approver).then((user)=> {
          if ( user && user.group ) {
            self.add(user.group);
          } else {
            self.add(approver);
          }
        });
      },
      visibility: function(approver) {
        return approver ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      },
    },
    {
      class: 'Object',
      javaType: 'Object',
      name: 'objId',
      permissionRequired: true,
      documentation: 'id of the object that needs approval.',
      tableWidth: 150,
      tableCellFormatter: function(objId) {
        let self = this;
        var userId = parseInt(objId);
        if ( !! userId ) {
          this.__subSubContext__.userDAO.find(userId).then(function(a) {

          if ( a != undefined ) {
            self.add(a.toSummary());
          } else {
            self.add(objId);
          }
          }).catch(function(err) {
            self.add(objId);
          });
        }
      }
    },
    {
      class: 'String',
      name: 'daoKey',
      permissionRequired: true,
      documentation: `Used internally in approvalDAO to point where requested object can be found.
      Should not be used to retrieve approval requests for a given objects
      since an object can have multiple requests of different nature.`
    },
    {
      class: 'String',
      name: 'classification',
      label: 'Approval Type',
      section: 'requestDetails',
      tableWidth: 150,
      documentation: `Should be unique to a certain type of requests and created within a single rule.
      For example "IdentityMind Business approval".
      When retrieving approval requests from a dao, do not use daoKey, use classification instead:
      mlang.AND(
        EQ(ApprovalRequest.OBJ_ID, objectId),
        EQ(ApprovalRequest.REQUEST_REFERENCE, "reference")
      )`,
      gridColumns: 4,
      visibility: function(classification) {
        return classification ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Int',
      name: 'points',
      documentation: `Specific to each ApprovalRequest object.
      Determines the weight of the approved request depending on the approver's role.
      Future: populated in approvalRequestDAO pipeline based on configurations.
      Currentely populated as 1.`,
      gridColumns: 4,
      section: 'basicInformation',
      visibility: function(points) {
        return points ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Int',
      name: 'requiredPoints',
      value: 1,
      gridColumns: 4,
      section: 'basicInformation',
      documentation: `Defines how many approvers required and approvers' ranks.
      E.g. when set to 10:
      1) 10 approval requests with "points" set to 1.
      2) 2 approval requests with "points" set to 3 and 1 approval request with "points" set to 5.
      etc.
      Deafults to 1 meaning only one approval of any approver rank is required by default.`,
      visibility: function(requiredPoints) {
        return requiredPoints ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Int',
      name: 'requiredRejectedPoints',
      value: 1,
      gridColumns: 4,
      section: 'basicInformation',
      visibility: function(requiredRejectedPoints) {
        return requiredRejectedPoints ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.Group',
      name: 'group',
      documentation: `When set, each user in the group will receive a request for approval.
      If "approver" property is set, "group" property is ignored.`,
      section: 'supportDetails',
      visibility: function(group) {
        return group ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Enum',
      of: 'foam.nanos.approval.ApprovalStatus',
      name: 'status',
      value: 'REQUESTED',
      section: 'requestDetails',
      javaFactory: 'return foam.nanos.approval.ApprovalStatus.REQUESTED;',
      visibility: 'RO'
    },
    {
      class: 'String',
      name: 'memo',
      view: { class: 'foam.u2.tag.TextArea', rows: 5, cols: 80 },
      documentation: 'Meant to be used for explanation on why request was approved/rejected',
      section: 'supportDetails',
      visibility: function(memo, status) {
        if ( status == foam.nanos.approval.ApprovalStatus.REQUESTED ) {
          return foam.u2.DisplayMode.RW;
        } else if ( memo ) {
          return foam.u2.DisplayMode.R0;
        } else {
          return foam.u2.DisplayMode.HIDDEN;
        }
      }
    },
    {
      class: 'String',
      name: 'description',
      documentation: `Approval request description.`,
      tableWidth: 200,
      section: 'requestDetails',
      visibility: function(description) {
        return description ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
   },
    {
      class: 'String',
      name: 'token',
      documentation: 'token in email for ‘click to approve’.',
      section: 'basicInformation',
      permissionRequired: true
    },
    {
      class: 'DateTime',
      name: 'created',
      section: 'supportDetails',
      gridColumns: 6,
      visibility: function(created) {
        return created ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'DateTime',
      name: 'lastModified',
      gridColumns: 6,
      section: 'supportDetails',
      visibility: function(lastModified) {
        return lastModified ?
          foam.u2.DisplayMode.RO :
          foam.u2.DisplayMode.HIDDEN;
      }
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.User',
      name: 'createdBy',
      section: 'supportDetails',
      permissionRequired: true
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.User',
      name: 'createdByAgent',
      section: 'supportDetails',
      permissionRequired: true
    },
    {
      class: 'Reference',
      of: 'foam.nanos.auth.User',
      name: 'lastModifiedBy',
      section: 'supportDetails',
      permissionRequired: true
    },
    {
      class: 'String',
      name: 'refObj',
      transient: true,
      expression: function(daoKey, objId) {
        return daoKey + ':' + objId;
      },
      javaGetter: `
        return getDaoKey() + ": " + getObjId();
      `,
      permissionRequired: true
    },
    {
      class: 'String',
      name: 'daoKey_',
      permissionRequired: true,
      factory: function(o, n) {
        var key = this.daoKey;
        var X = this.ctrl.__subContext__;
        // FIXME: change to a better implementation
        if ( ! X[key] ) {
          // if DAO doesn't exist in context, change daoKey from localMyDAO
          // (server-side) to myDAO (accessible on front-end)
          key = key.substring(5, 6).toLowerCase() + key.substring(6);
        }
        return key;
      }
    },
    {
      name: 'referenceObj_',
      permissionRequired: true,
      expression: function() {
        var X = this.ctrl.__subContext__;
        var objId = X[this.daoKey_].of.ID.type === 'Long' ? parseInt(this.objId) : this.objId;

        X[this.daoKey_]
          .find(objId)
          .then((obj) => {
            if (
              // if approvalRequest is rejected don't show ViewReference
              this.status === foam.nanos.approval.ApprovalStatus.REJECTED
              ||
              // if approvalRequest is for remove and has been either approved/rejected
              (this.status === foam.nanos.approval.ApprovalStatus.APPROVED
                &&
              this.operation === foam.nanos.ruler.Operations.REMOVE)
            ) {
              this.referenceObj_ = '';
              return;
            }
            this.referenceObj_ = obj;
          })
          .catch((err) => {
            console.warn(err.message || err);
          });
        return '';
    }
  }
  ],

  methods: [
    {
      name: 'validate',
      args: [
        {
          name: 'x',
          type: 'Context'
        }
      ],
      javaCode: `Logger logger = (Logger) x.get("logger");
DAO dao = (DAO) x.get(getDaoKey());
if ( dao == null ) {
  logger.error(this.getClass().getSimpleName(), "DaoKey not found", getDaoKey());
  throw new RuntimeException("Invalid dao key for the approval request object.");
}
FObject obj = dao.inX(x).find(getObjId());
if ( obj == null ) {
  logger.error(this.getClass().getSimpleName(), "ObjId not found", getObjId());
  throw new RuntimeException("Invalid object id.");
}
      `
    }
  ],

  actions: [
    {
      name: 'approve',
      label: 'Approve',
      section: 'requestDetails',
      code: function() {
        this.status = this.ApprovalStatus.APPROVED;
        this.approvalRequestDAO.put(this);
        this.approvalRequestDAO.cmd(this.AbstractDAO.RESET_CMD);
      },
      tableWidth: 100
    },
    {
      name: 'reject',
      label: 'Reject',
      section: 'requestDetails',
      code: function() {
        this.status = this.ApprovalStatus.REJECTED;
        this.approvalRequestDAO.put(this);
        this.approvalRequestDAO.cmd(this.AbstractDAO.RESET_CMD);
      },
      tableWidth: 100
    },
    {
      name: 'viewReference',
      isDefault: true,
      isAvailable: function(referenceObj_) {
        return !! referenceObj_;
      },
      code: function(X) {
        var obj = this.referenceObj_;
        // If the dif of objects is calculated and stored in Map(obj.propertiesToUpdate),
        // this is for updating object approvals
        if ( obj.propertiesToUpdate ) {
          // then here we created custom view to display these properties
          X.stack.push({
            class: 'foam.nanos.approval.PropertiesToUpdateView',
            propObject: obj.propertiesToUpdate,
            objId: obj.objId,
            daoKey: obj.daoKey,
            title: 'Updated Properties and Changes'
          });
          return;
        }
        // else pass general view with modeled data for display
        // this is for create, deleting object approvals
        X.stack.push({
          class: 'foam.comics.v2.DAOSummaryView',
          data: obj,
          of: obj.cls_,
          config: foam.comics.v2.DAOControllerConfig.create({
            daoKey: this.daoKey_,
            of: obj.cls_,
            editEnabled: false,
            createEnabled: false,
            deleteEnabled: false
          })
        });
      },
      tableWidth: 100
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 // TODO:
 // permission dependency graph is too complex given @group permissions
 // so instead of maintaining graph just invalidate all states and then just
 // recalculate lazily whenever any checkbox is clicked
 // invalidated values should still maintain their previous value while
 // being recalculated
foam.CLASS({
  package: 'foam.nanos.auth',
  name: 'PermissionTableView',
  extends: 'foam.u2.Controller',

  imports: [
    'auth',
    'groupDAO',
    'permissionDAO',
    'user'
  ],

  requires: [
    'foam.graphics.Label',
    'foam.graphics.ScrollCView',
    'foam.nanos.auth.Group',
    'foam.nanos.auth.Permission'
  ],

  constants: {
    ROWS: 26
  },

  css: `
    ^ thead th {
      background: white;
      padding: 0;
      text-align: center;
    }

    ^ tbody td {
      text-align: center;
    }

    tbody {
       overflow: auto;
       width: 100%;
       height: 150px;
     }

    ^ tbody tr { background: white; }

    ^ .foam-u2-md-CheckBox {
      margin: 1px;
      border: none;
    }

    ^ .foam-u2-md-CheckBox:hover {
      background: #FFCCCC;
    }

    ^ tbody tr:hover, ^hovered {
      background: #ccc;
    }

    ^ table {
       box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
       width: auto;
       border: 0;
      }

    ^header {
      box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
      background: white;
      padding: 8px;
      margin: 8px 0;
    }

    ^header input { float: right }

    ^ .permissionHeader {
      color: #444;
      text-align: left;
      padding-left: 6px;
    }

    ^ .property-groupQuery {
      margin-left: 8px;
    }
  `,

  properties: [
    {
      class: 'String',
      name: 'query',
      postSet: function() { this.skip = 0; },
      view: {
        class: 'foam.u2.TextField',
        type: 'Search',
        placeholder: 'Permission Search',
        onKey: true
      }
    },
    {
      class: 'String',
      name: 'groupQuery',
      view: {
        class: 'foam.u2.TextField',
        type: 'Search',
        placeholder: 'Group Search',
        onKey: true
      }
    },
    {
      name: 'selectedGroup',
      documentation: 'Array for managing checkbox value on groups filter'
    },
    {
      name: 'columns_',
      documentation: 'Array for managing checked groups'
    },
    {
      name: 'textData',
      documentation: 'input text value by user'

    },
    {
      class: 'Map',
      name: 'gpMap'
    },
    {
      class: 'Map',
      name: 'gMap'
    },
    {
      class: 'Map',
      name: 'pMap'
    },
    {
      class: 'Int',
      name: 'skip'
    },
    'ps', /* permissions array */
    'gs', /* groups array */
    'currentGroup',
    {
      name: 'filteredPs',
      expression: function(ps, query) {
        query = query.trim();
        return ps.filter(function(p) {
          return query == '' || p.id.indexOf(query) != -1;
        });
      }
    },
    {
      name: 'filteredRows',
      expression: function(filteredPs) {
        return filteredPs.length;
      }
    }
  ],

  methods: [
    function initMatrix() {
      var ps   = this.filteredPs, gs = this.gs;
      var self = this;
      this
        .addClass(this.myClass())
        .start()
          .style({display:'grid', justifyContent: 'center'})
          .start()
            .style({gridColumn: '1/span 1', gridRow: '1/span 1'})
            .addClass(this.myClass('header'))
            .add('Permission Matrix')
            .add(this.GROUP_QUERY, ' ', this.QUERY)
          .end()
          .start('table')
            .on('wheel', this.onWheel)
            .style({gridColumn: '1/span 1', gridRow: '2/span 1'})
            .start('thead')
              .start('tr')
                .start('th')
                  .attrs({colspan:1000})
                  .style({textAlign: 'left', padding: '8px', fontWeight: 400})
                  .add(gs.length, ' groups, ', ps.length, ' permissions', self.filteredRows$.map(function(rows) { return rows == ps.length ?  '' : (', ' + rows + ' selected'); }))
                  .start()
                    .style({float: 'right'})
                    .add('⋮')
                  .end()
                .end()
              .end()
              .start('tr')
                .start('th').style({minWidth: '510px'}).end()
                .call(function() { self.initTableColumns.call(this, gs, self); })
              .end()
            .end()
            .add(this.slot(function(skip, filteredPs) {
              var count = 0;
              return self.E('tbody').forEach(filteredPs, function(p) {
                if ( count > self.skip + self.ROWS ) return;
                if ( count < self.skip ) { count++; return; }
                count++;
                this.start('tr')
                  .start('td')
                    .addClass('permissionHeader')
                    .attrs({title: p.description})
                    .add(p.id)
                  .end()
                  .forEach(gs, function(g) {
                    this.start('td')
                      .show(self.groupQuery$.map(function(q) {
                        return q == '' || g.id.indexOf(q) != -1;
                      }))
                      .on('mouseover', function() { self.currentGroup = g; })
                      .on('mouseout', function() { if ( self.currentGroup === g ) self.currentGroup = ''; })
                      .enableClass(self.myClass('hovered'), self.currentGroup$.map(function(cg) { return cg === g; } ))
                      .attrs({title: g.id + ' : ' + p.id})
                      .tag(self.createCheckBox(p, g))
                    .end();
                  })
                .end();
              });
            }))
          .end()
          .start(self.ScrollCView.create({
            value$: self.skip$,
            extent: self.ROWS,
            height: self.ROWS*21,
            width: 26,
            size$: self.filteredRows$.map(function(m){return m-1;})
          }))
            .style({gridColumn: '2/span 1', gridRow: '2/span 2', 'margin-top':'236px'})
          .end()
        .end();
    },

    // * -> null, foo.bar -> foo.*, foo.* -> *
    function getParentGroupPermission(p, g) {
      var pid = p.id;
      while ( true ) {
        while ( pid.endsWith('.*') ) {
          pid = pid.substring(0, pid.length-2);
        }
        if ( pid == '*' ) return null;
        var i = pid.lastIndexOf('.');
        pid = ( i == -1 ) ? '*' : pid.substring(0, i) + '.*';
        if ( pid in this.pMap ) return this.getGroupPermission(g, this.pMap[pid]);
      }
    },

    function getGroupPermission(g, p) {
      var key  = p.id + ':' + g.id;
      var data = this.gpMap[key];
      var self = this;

      if ( ! data ) {
        data = this.GroupPermission.create({
          checked: this.checkPermissionForGroup(p.id, g)
        });

        data.checked$.sub(function() {
          self.updateGroup(p, g, data.checked$, self);
        });

        // data.impliedByParentPermission = ! data.checked && g.implies(p.id);

        // Parent Group Inheritance
        this.dependOnGroup(g.parent, p, data);

        // Parent Permission Inheritance (wildcarding)
        var pParent = this.getParentGroupPermission(p, g);
        if ( pParent ) {
          function update2() {
            data.impliedByParentPermission = pParent.granted;
          }
          update2();
          this.onDetach(pParent.granted$.sub(update2));
        }

        this.gpMap[key] = data;
      }

      return data;
    },

    function dependOnGroup(g, p, data) {
      if ( ! g ) return;

      var a = this.gMap[g];
      if ( a ) {
        var parent = g && this.getGroupPermission(a, p);
        if ( parent ) {
          function update() {
            if ( parent.granted ) {
              data.impliedByGroups[parent.id] = true;
            } else {
              delete data.impliedByGroups[parent.id];
            }
            data.impliedByGroup = !! Object.keys(data.impliedByGroups).length;
          }
          update();
          this.onDetach(parent.granted$.sub(update));
        }
      }
    },

    function createCheckBox(p, g) {
      // Disable adding a group role to that group itself.
      // TODO: should be protected in the model as well to prevent
      // updating through Group GUI, DIG or API. Also, should prevent
      // loops.
      if ( p.id == '@' + g.id ) return this.E().add('X');
      var self = this;
      return function() {
        return self.GroupPermissionView.create({data: self.getGroupPermission(g, p)});
      };
    },

    function initTableColumns(gs, matrix) {
      var self = this;
      this.forEach(gs, function(g) {
        this.start('th')
          .show(matrix.groupQuery$.map(function(q) {
            return q == '' || g.id.indexOf(q) != -1;
          }))
          .attrs({title: g.description})
          .call(function() {
            var cv = foam.graphics.Box.create({
              color$: matrix.currentGroup$.map(function(cg) { return cg === g ? '#ccc' : 'white'; }),
              autoRepaint: true,
              width: 20,
              height: 200});
            var l  = foam.graphics.Label.create({
              text: g.id,
              x: 25,
              y: 8,
              color: 'black',
              font: '300 16px Roboto',
              width: 200,
              height: 20,
              rotation: -Math.PI/2});
            cv.add(l);
            this.add(cv);
          })
        .end();
      });
    },

    function initE() {
      this.SUPER();
      var self = this;

      this.groupDAO.orderBy(this.Group.ID).select().then(function(gs) {
        for ( var i = 0 ; i < gs.array.length ; i++ ) {
          self.gMap[gs.array[i].id] = gs.array[i];
        }
        self.permissionDAO.orderBy(self.Permission.ID).select().then(function(ps) {
          for ( var i = 0 ; i < ps.array.length ; i++ ) {
            self.pMap[ps.array[i].id] = ps.array[i];
          }
          self.gs = gs.array;
          self.ps = ps.array;
          self.initMatrix();
        })
      });
    },

    function checkPermissionForGroup(permissionId, group) {
      for ( i = 0 ; i < group.permissions.length ; i++ ) {
        if ( permissionId == group.permissions[i].id ) {
          return true;
        }
      }
    },

    function updateGroup(p_, g_, data, self) {
      var dao = this.groupDAO;
      var e   = foam.mlang.Expressions.create();

      dao.find(g_.id).then(function(group) {
        // Remove permission if found
        var permissions = group.permissions.filter(function(p) {
          return p.id != p_.id;
        });

        // parents' permissions
        group.parent$find.then(function(groupParent) {
          if ( groupParent != undefined ) {
              permissions += groupParent.permissions.filter(function(gp) {
                return gp.id == p_.id;
              });
          }
        });

        // Add if requested
        if ( data.get() ) permissions.push(p_);

        group.permissions = permissions;
        dao.put(group);
      });
    }
  ],

  listeners: [
    {
      name: 'onWheel',
      code: function(e) {
        var negative = e.deltaY < 0;
        // Convert to rows, rounding up. (Therefore minumum 1.)
        var rows = Math.ceil(Math.abs(e.deltaY) / 40);
        this.skip = Math.max(0, this.skip + (negative ? -rows : rows));
        if ( e.deltaY !== 0 ) e.preventDefault();
      }
    }
  ],

  classes: [
    {
      name: 'GroupPermission',
      properties: [
        {
          class: 'Boolean',
          name: 'checked'
        },
        {
          class: 'Boolean',
          name: 'impliedByParentPermission'
        },
        {
          class: 'Map',
          name: 'impliedByGroups'
        },
        {
          class: 'Boolean',
          name: 'impliedByGroup'
        },
        {
          class: 'Boolean',
          name: 'implied',
          expression: function(impliedByParentPermission, impliedByGroup) {
            return impliedByParentPermission || impliedByGroup;
          }
        },
        {
          class: 'Boolean',
          name: 'granted',
          expression: function(checked, implied) {
            return checked || implied;
          }
        }
      ]
    },
    {
      name: 'GroupPermissionView',
      extends: 'foam.u2.View',
      css: `
        ^:hover { background: #f55 }
        ^checked { color: #4885ff }
        ^implied { color: gray }
      `,
      methods: [
        function initE() {
          this.SUPER();
          this.
            addClass(this.myClass()).
            style({width: '18px', height: '18px'}).
            enableClass(this.myClass('implied'), this.data.checked$, true).
            enableClass(this.myClass('checked'), this.data.checked$).
            add(this.slot(function(data$granted) {
              return data$granted ? '✓' : '';
            })).
            on('click', this.onClick);
        }
      ],
      listeners: [
        function onClick() {
          this.data.checked = ! this.data.checked;
        }
      ]
    }
  ]
});
<commit_msg>Fix PermissionMatrix to show permissions again.<commit_after>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 // TODO:
 // permission dependency graph is too complex given @group permissions
 // so instead of maintaining graph just invalidate all states and then just
 // recalculate lazily whenever any checkbox is clicked
 // invalidated values should still maintain their previous value while
 // being recalculated
foam.CLASS({
  package: 'foam.nanos.auth',
  name: 'PermissionTableView',
  extends: 'foam.u2.Controller',

  imports: [
    'auth',
    'groupDAO',
    'groupPermissionJunctionDAO',
    'permissionDAO',
    'user'
  ],

  requires: [
    'foam.graphics.Label',
    'foam.graphics.ScrollCView',
    'foam.nanos.auth.Group',
    'foam.nanos.auth.Permission'
  ],

  constants: {
    ROWS: 26
  },

  css: `
    ^ thead th {
      background: white;
      padding: 0;
      text-align: center;
    }

    ^ tbody td {
      text-align: center;
    }

    tbody {
       overflow: auto;
       width: 100%;
       height: 150px;
     }

    ^ tbody tr { background: white; }

    ^ .foam-u2-md-CheckBox {
      margin: 1px;
      border: none;
    }

    ^ .foam-u2-md-CheckBox:hover {
      background: #FFCCCC;
    }

    ^ tbody tr:hover, ^hovered {
      background: #ccc;
    }

    ^ table {
       box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
       width: auto;
       border: 0;
      }

    ^header {
      box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
      background: white;
      padding: 8px;
      margin: 8px 0;
    }

    ^header input { float: right }

    ^ .permissionHeader {
      color: #444;
      text-align: left;
      padding-left: 6px;
    }

    ^ .property-groupQuery {
      margin-left: 8px;
    }
  `,

  properties: [
    {
      class: 'String',
      name: 'query',
      postSet: function() { this.skip = 0; },
      view: {
        class: 'foam.u2.TextField',
        type: 'Search',
        placeholder: 'Permission Search',
        onKey: true
      }
    },
    {
      class: 'String',
      name: 'groupQuery',
      view: {
        class: 'foam.u2.TextField',
        type: 'Search',
        placeholder: 'Group Search',
        onKey: true
      }
    },
    {
      name: 'selectedGroup',
      documentation: 'Array for managing checkbox value on groups filter'
    },
    {
      name: 'columns_',
      documentation: 'Array for managing checked groups'
    },
    {
      name: 'textData',
      documentation: 'input text value by user'

    },
    {
      class: 'Map',
      name: 'gpMap'
    },
    {
      class: 'Map',
      name: 'gMap'
    },
    {
      class: 'Map',
      name: 'pMap'
    },
    {
      class: 'Int',
      name: 'skip'
    },
    'ps', /* permissions array */
    'gs', /* groups array */
    'currentGroup',
    {
      name: 'filteredPs',
      expression: function(ps, query) {
        query = query.trim();
        return ps.filter(function(p) {
          return query == '' || p.id.indexOf(query) != -1;
        });
      }
    },
    {
      name: 'filteredRows',
      expression: function(filteredPs) {
        return filteredPs.length;
      }
    }
  ],

  methods: [
    async function initMatrix() {
      var ps   = this.filteredPs, gs = this.gs;
      var self = this;
      var perms = await this.groupPermissionJunctionDAO.select();
      perms.array.forEach(perm => {
        var g = perm.sourceId, p = perm.targetId;
        var key = p + ':' + g;

        var data = this.GroupPermission.create({
          checked: true
        });

        this.gpMap[key] = data;
      });

      this
        .addClass(this.myClass())
        .start()
          .style({display:'grid', justifyContent: 'center'})
          .start()
            .style({gridColumn: '1/span 1', gridRow: '1/span 1'})
            .addClass(this.myClass('header'))
            .add('Permission Matrix')
            .add(this.GROUP_QUERY, ' ', this.QUERY)
          .end()
          .start('table')
            .on('wheel', this.onWheel)
            .style({gridColumn: '1/span 1', gridRow: '2/span 1'})
            .start('thead')
              .start('tr')
                .start('th')
                  .attrs({colspan:1000})
                  .style({textAlign: 'left', padding: '8px', fontWeight: 400})
                  .add(gs.length, ' groups, ', ps.length, ' permissions', self.filteredRows$.map(function(rows) { return rows == ps.length ?  '' : (', ' + rows + ' selected'); }))
                  .start()
                    .style({float: 'right'})
                    .add('⋮')
                  .end()
                .end()
              .end()
              .start('tr')
                .start('th').style({minWidth: '510px'}).end()
                .call(function() { self.initTableColumns.call(this, gs, self); })
              .end()
            .end()
            .add(this.slot(function(skip, filteredPs) {
              var count = 0;
              return self.E('tbody').forEach(filteredPs, function(p) {
                if ( count > self.skip + self.ROWS ) return;
                if ( count < self.skip ) { count++; return; }
                count++;
                this.start('tr')
                  .start('td')
                    .addClass('permissionHeader')
                    .attrs({title: p.description})
                    .add(p.id)
                  .end()
                  .forEach(gs, function(g) {
                    this.start('td')
                      .show(self.groupQuery$.map(function(q) {
                        return q == '' || g.id.indexOf(q) != -1;
                      }))
                      .on('mouseover', function() { self.currentGroup = g; })
                      .on('mouseout', function() { if ( self.currentGroup === g ) self.currentGroup = ''; })
                      .enableClass(self.myClass('hovered'), self.currentGroup$.map(function(cg) { return cg === g; } ))
                      .attrs({title: g.id + ' : ' + p.id})
                      .tag(self.createCheckBox(p, g))
                    .end();
                  })
                .end();
              });
            }))
          .end()
          .start(self.ScrollCView.create({
            value$: self.skip$,
            extent: self.ROWS,
            height: self.ROWS*21,
            width: 26,
            size$: self.filteredRows$.map(function(m){return m-1;})
          }))
            .style({gridColumn: '2/span 1', gridRow: '2/span 2', 'margin-top':'236px'})
          .end()
        .end();
    },

    // * -> null, foo.bar -> foo.*, foo.* -> *
    function getParentGroupPermission(p, g) {
      var pid = p.id;
      while ( true ) {
        while ( pid.endsWith('.*') ) {
          pid = pid.substring(0, pid.length-2);
        }
        if ( pid == '*' ) return null;
        var i = pid.lastIndexOf('.');
        pid = ( i == -1 ) ? '*' : pid.substring(0, i) + '.*';
        if ( pid in this.pMap ) return this.getGroupPermission(g, this.pMap[pid]);
      }
    },

    function getGroupPermission(g, p) {
      var key  = p.id + ':' + g.id;
      var data = this.gpMap[key];
      var self = this;

      if ( ! data ) {
        data = this.GroupPermission.create({
          checked: false
        });

        data.checked$.sub(function() {
          self.updateGroup(p, g, data.checked$, self);
        });

        // data.impliedByParentPermission = ! data.checked && g.implies(p.id);

        // Parent Group Inheritance
        this.dependOnGroup(g.parent, p, data);

        // Parent Permission Inheritance (wildcarding)
        var pParent = this.getParentGroupPermission(p, g);
        if ( pParent ) {
          function update2() {
            data.impliedByParentPermission = pParent.granted;
          }
          update2();
          this.onDetach(pParent.granted$.sub(update2));
        }

        this.gpMap[key] = data;
      }

      return data;
    },

    function dependOnGroup(g, p, data) {
      if ( ! g ) return;

      var a = this.gMap[g];
      if ( a ) {
        var parent = g && this.getGroupPermission(a, p);
        if ( parent ) {
          function update() {
            if ( parent.granted ) {
              data.impliedByGroups[parent.id] = true;
            } else {
              delete data.impliedByGroups[parent.id];
            }
            data.impliedByGroup = !! Object.keys(data.impliedByGroups).length;
          }
          update();
          this.onDetach(parent.granted$.sub(update));
        }
      }
    },

    function createCheckBox(p, g) {
      // Disable adding a group role to that group itself.
      // TODO: should be protected in the model as well to prevent
      // updating through Group GUI, DIG or API. Also, should prevent
      // loops.
      if ( p.id == '@' + g.id ) return this.E().add('X');
      var self = this;
      return function() {
        return self.GroupPermissionView.create({data: self.getGroupPermission(g, p)});
      };
    },

    function initTableColumns(gs, matrix) {
      var self = this;
      this.forEach(gs, function(g) {
        this.start('th')
          .show(matrix.groupQuery$.map(function(q) {
            return q == '' || g.id.indexOf(q) != -1;
          }))
          .attrs({title: g.description})
          .call(function() {
            var cv = foam.graphics.Box.create({
              color$: matrix.currentGroup$.map(function(cg) { return cg === g ? '#ccc' : 'white'; }),
              autoRepaint: true,
              width: 20,
              height: 200});
            var l  = foam.graphics.Label.create({
              text: g.id,
              x: 25,
              y: 8,
              color: 'black',
              font: '300 16px Roboto',
              width: 200,
              height: 20,
              rotation: -Math.PI/2});
            cv.add(l);
            this.add(cv);
          })
        .end();
      });
    },

    function initE() {
      this.SUPER();
      var self = this;

      this.groupDAO.orderBy(this.Group.ID).select().then(function(gs) {
        for ( var i = 0 ; i < gs.array.length ; i++ ) {
          self.gMap[gs.array[i].id] = gs.array[i];
        }
        self.permissionDAO.orderBy(self.Permission.ID).select().then(function(ps) {
          for ( var i = 0 ; i < ps.array.length ; i++ ) {
            self.pMap[ps.array[i].id] = ps.array[i];
          }
          self.gs = gs.array;
          self.ps = ps.array;
          self.initMatrix();
        })
      });
    },

    function updateGroup(p_, g_, data, self) {
      var dao = this.groupDAO;
      var e   = foam.mlang.Expressions.create();

      dao.find(g_.id).then(function(group) {
        // Remove permission if found
        var permissions = group.permissions.filter(function(p) {
          return p.id != p_.id;
        });

        // parents' permissions
        group.parent$find.then(function(groupParent) {
          if ( groupParent != undefined ) {
              permissions += groupParent.permissions.filter(function(gp) {
                return gp.id == p_.id;
              });
          }
        });

        // Add if requested
        if ( data.get() ) permissions.push(p_);

        group.permissions = permissions;
        dao.put(group);
      });
    }
  ],

  listeners: [
    {
      name: 'onWheel',
      code: function(e) {
        var negative = e.deltaY < 0;
        // Convert to rows, rounding up. (Therefore minumum 1.)
        var rows = Math.ceil(Math.abs(e.deltaY) / 40);
        this.skip = Math.max(0, this.skip + (negative ? -rows : rows));
        if ( e.deltaY !== 0 ) e.preventDefault();
      }
    }
  ],

  classes: [
    {
      name: 'GroupPermission',
      properties: [
        {
          class: 'Boolean',
          name: 'checked'
        },
        {
          class: 'Boolean',
          name: 'impliedByParentPermission'
        },
        {
          class: 'Map',
          name: 'impliedByGroups'
        },
        {
          class: 'Boolean',
          name: 'impliedByGroup'
        },
        {
          class: 'Boolean',
          name: 'implied',
          expression: function(impliedByParentPermission, impliedByGroup) {
            return impliedByParentPermission || impliedByGroup;
          }
        },
        {
          class: 'Boolean',
          name: 'granted',
          expression: function(checked, implied) {
            return checked || implied;
          }
        }
      ]
    },
    {
      name: 'GroupPermissionView',
      extends: 'foam.u2.View',
      css: `
        ^:hover { background: #f55 }
        ^checked { color: #4885ff }
        ^implied { color: gray }
      `,
      methods: [
        function initE() {
          this.SUPER();
          this.
            addClass(this.myClass()).
            style({width: '18px', height: '18px'}).
            enableClass(this.myClass('implied'), this.data.checked$, true).
            enableClass(this.myClass('checked'), this.data.checked$).
            add(this.slot(function(data$granted) {
              return data$granted ? '✓' : '';
            })).
            on('click', this.onClick);
        }
      ],
      listeners: [
        function onClick() {
          this.data.checked = ! this.data.checked;
        }
      ]
    }
  ]
});
<|endoftext|>


<commit_before>import React from 'react'
import Relay from 'react-relay'
import {Styles, Divider, FloatingActionButton, RaisedButton, FlatButton, Card, CardTitle, CardText, CardActions, FontIcon} from 'material-ui'
import GCForm from './forms/GCForm'
import Form from 'react-formal'
import yup from 'yup'
import moment from 'moment'
import {BernieTheme} from './styles/bernie-theme'
import {BernieText, BernieColors} from './styles/bernie-css'
import stripScripts from '../helpers/stripScripts'

const style = {
  marginLeft: 20,
}

class EventsDashboard extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight
    }

    window.addEventListener('resize', this._handleResize)
  }

  _handleResize = (e) => {
    this.setState({
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight
    })
  }

  renderEvents() {
    const events = this.props.currentUser.relatedPerson ? this.props.currentUser.relatedPerson.hostedEvents : []
    if (events.length === 0)
      return

No Events

You haven't signed up to host any events yet! Click here to create your first event.

    return events.map((event) => {
      const utcOffset = event.localUTCOffset || 0
      const timezone = event.localTimezone || 'UTC'
      const offsetDate = moment(event.startDate).utcOffset(utcOffset)
      const formattedDate = offsetDate.format('l LT')
      return (
        <Card key={event.id} style={{margin: 5, width: this.state.windowWidth - 10}}>
          <CardTitle
            title={event.name}
            subtitle={formattedDate}
            actAsExpander={true}
            showExpandableButton={true}
          />
          <CardText expandable={true}>

          </CardText>
          <CardActions expandable={true}>
            <FlatButton
              label='View'
              onTouchTap={() => this.props.history.push('/events/' + event.eventIdObfuscated)}
            />
            <FlatButton label="Edit"/>
            <FlatButton label="Delete"/>
            <FlatButton
              label="Send Turnout Email"
              onTouchTap={() => this.props.history.push('/events/' + event.eventIdObfuscated + '/request-email')}
            />
            <FlatButton
              label="Upload Sign In Sheets"
              onTouchTap={() => this.props.history.push('/events/' + event.eventIdObfuscated + '/upload')}
            />
          </CardActions>
        </Card>
      )
    })
  }

  render() {
    return (

        {this.renderEvents()}
        <FloatingActionButton style={{marginRight: 20, position: 'absolute', bottom: 20, right: 10}} linkButton={true} href='/events/create' >
          <FontIcon className="material-icons">add</FontIcon>
        </FloatingActionButton>

    )
  }
}

export default Relay.createContainer(EventsDashboard, {
  fragments: {
    currentUser: () => Relay.QL`
      fragment on User {
        id
        email
        relatedPerson {
          hostedEvents {
            name
            id
            host {
              id
              firstName
              lastName
              email
            }
            eventType {
              id
              name
            }
            eventIdObfuscated
            flagApproval
            isOfficial
            description
            venueName
            latitude
            longitude
            venueZip
            venueCity
            venueState
            venueAddr1
            venueAddr2
            venueCountry
            venueDirections
            createDate
            startDate
            localTimezone
            localUTCOffset
            duration
            capacity
            attendeeVolunteerShow
            attendeeVolunteerMessage
            isSearchable
            publicPhone
            contactPhone
            hostReceiveRsvpEmails
            rsvpUseReminderEmail
            rsvpEmailReminderHours
            attendeesCount
            attendees {
              firstName
              lastName
              phone
              email
              address {
                city
                state
                zip
              }
            }
          }
        }
      }
    `
  }
})<commit_msg>Keep FAB fixed<commit_after>import React from 'react'
import Relay from 'react-relay'
import {Styles, Divider, FloatingActionButton, RaisedButton, FlatButton, Card, CardTitle, CardText, CardActions, FontIcon} from 'material-ui'
import GCForm from './forms/GCForm'
import Form from 'react-formal'
import yup from 'yup'
import moment from 'moment'
import {BernieTheme} from './styles/bernie-theme'
import {BernieText, BernieColors} from './styles/bernie-css'
import stripScripts from '../helpers/stripScripts'

const style = {
  marginLeft: 20,
}

class EventsDashboard extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight
    }

    window.addEventListener('resize', this._handleResize)
  }

  _handleResize = (e) => {
    this.setState({
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight
    })
  }

  renderEvents() {
    const events = this.props.currentUser.relatedPerson ? this.props.currentUser.relatedPerson.hostedEvents : []
    if (events.length === 0)
      return

No Events

You haven't signed up to host any events yet! Click here to create your first event.

    return events.map((event) => {
      const utcOffset = event.localUTCOffset || 0
      const timezone = event.localTimezone || 'UTC'
      const offsetDate = moment(event.startDate).utcOffset(utcOffset)
      const formattedDate = offsetDate.format('l LT')
      return (
        <Card key={event.id} style={{margin: 5, width: this.state.windowWidth - 10}}>
          <CardTitle
            title={event.name}
            subtitle={formattedDate}
            actAsExpander={true}
            showExpandableButton={true}
          />
          <CardText expandable={true}>

          </CardText>
          <CardActions expandable={true}>
            <FlatButton
              label='View'
              onTouchTap={() => this.props.history.push('/events/' + event.eventIdObfuscated)}
            />
            <FlatButton label="Edit"/>
            <FlatButton label="Delete"/>
            <FlatButton
              label="Send Turnout Email"
              onTouchTap={() => this.props.history.push('/events/' + event.eventIdObfuscated + '/request-email')}
            />
            <FlatButton
              label="Upload Sign In Sheets"
              onTouchTap={() => this.props.history.push('/events/' + event.eventIdObfuscated + '/upload')}
            />
          </CardActions>
        </Card>
      )
    })
  }

  render() {
    return (

        {this.renderEvents()}
        <FloatingActionButton style={{marginRight: 20, position: 'fixed', bottom: 20, right: 10}} linkButton={true} href='/events/create' >
          <FontIcon className="material-icons">add</FontIcon>
        </FloatingActionButton>

    )
  }
}

export default Relay.createContainer(EventsDashboard, {
  fragments: {
    currentUser: () => Relay.QL`
      fragment on User {
        id
        email
        relatedPerson {
          hostedEvents {
            name
            id
            host {
              id
              firstName
              lastName
              email
            }
            eventType {
              id
              name
            }
            eventIdObfuscated
            flagApproval
            isOfficial
            description
            venueName
            latitude
            longitude
            venueZip
            venueCity
            venueState
            venueAddr1
            venueAddr2
            venueCountry
            venueDirections
            createDate
            startDate
            localTimezone
            localUTCOffset
            duration
            capacity
            attendeeVolunteerShow
            attendeeVolunteerMessage
            isSearchable
            publicPhone
            contactPhone
            hostReceiveRsvpEmails
            rsvpUseReminderEmail
            rsvpEmailReminderHours
            attendeesCount
            attendees {
              firstName
              lastName
              phone
              email
              address {
                city
                state
                zip
              }
            }
          }
        }
      }
    `
  }
})<|endoftext|>